{"title":"Software Factory","link":[{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/","rel":"alternate"}},{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/feeds\/all.atom.xml","rel":"self"}}],"id":"https:\/\/www.softwarefactory-project.io\/","updated":"2024-12-09T00:00:00+00:00","entry":[{"title":"Secure Bubblewrap inside Kubernetes with ProcMount","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/secure-bubblewrap-inside-kubernetes-with-procmount.html","rel":"alternate"}},"published":"2024-12-09T00:00:00+00:00","updated":"2024-12-09T00:00:00+00:00","author":{"name":"tristanC"},"id":"tag:www.softwarefactory-project.io,2024-12-09:\/secure-bubblewrap-inside-kubernetes-with-procmount.html","summary":"<style type=\"text\/css\">\n\n  .literal {\n    border-radius: 6px;\n    padding: 1px 1px;\n    background-color: rgba(27,31,35,.05);\n  }\n\n<\/style><p>This post explores how to create nested containers securely inside\nKubernetes. In the previous post titled <a class=\"reference external\" href=\"https:\/\/www.softwarefactory-project.io\/recursive-namespaces-to-run-containers-inside-a-container.html\">Recursive namespaces to run\ncontainers inside a container<\/a> I showed how to create nested containers\nusing a rootless container runtimes like Podman \u2026<\/p>","content":"<style type=\"text\/css\">\n\n  .literal {\n    border-radius: 6px;\n    padding: 1px 1px;\n    background-color: rgba(27,31,35,.05);\n  }\n\n<\/style><p>This post explores how to create nested containers securely inside\nKubernetes. In the previous post titled <a class=\"reference external\" href=\"https:\/\/www.softwarefactory-project.io\/recursive-namespaces-to-run-containers-inside-a-container.html\">Recursive namespaces to run\ncontainers inside a container<\/a> I showed how to create nested containers\nusing a rootless container runtimes like Podman. In this post, I'll\ndemonstrate how to run the same workload with <a class=\"reference external\" href=\"https:\/\/kubernetes.io\/\">Kubernetes<\/a>.<\/p>\n<p>In two parts, I will present:<\/p>\n<ul class=\"simple\">\n<li>How to run Kubernetes from source.<\/li>\n<li>The ProcMountType feature to work around the original issue.<\/li>\n<\/ul>\n<div class=\"section\" id=\"context-and-problem-statement\">\n<h2>Context and problem statement<\/h2>\n<p>The context of this post is to deploy a service named zuul-executor for\nrunning CI builds securely inside Kubernetes, without requiring a\nprivileged security context.<\/p>\n<p>The problem is that this service performs build isolation locally using\n<a class=\"reference external\" href=\"https:\/\/github.com\/containers\/bubblewrap\">Bubblewrap<\/a>, which is similar to running a container inside a\ncontainer.<\/p>\n<\/div>\n<div class=\"section\" id=\"run-kubernetes-locally\">\n<h2>Run kubernetes locally<\/h2>\n<p>In this section, let's set up Kubernetes locally. On a fresh Fedora 41\nsystem, install the following requirements:<\/p>\n<div class=\"highlight\"><pre><span><\/span>$ sudo dnf install -y etcd crio crictl kubectl containernetworking-plugins\n$ sudo systemctl start crio\n<\/pre><\/div>\n<p>Then, start Kubernetes using the <em>local-up-cluster<\/em> script as follows:<\/p>\n<div class=\"highlight\"><pre><span><\/span>$ mkdir -p ~\/src\/github.com\/kubernetes; cd ~\/src\/github.com\/kubernetes\n$ git clone https:\/\/github.com\/kubernetes\/kubernetes\/\n$ cd kubernetes\n$ sudo env CGROUP_DRIVER=systemd CONTAINER_RUNTIME=remote CONTAINER_RUNTIME_ENDPOINT=&#39;unix:\/\/\/var\/run\/crio\/crio.sock&#39; \\\n    .\/hack\/local-up-cluster.sh\n...\nLocal Kubernetes cluster is running. Press Ctrl-C to shut it down.\n<\/pre><\/div>\n<p>\u2026 using the following test resource:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"nt\">apiVersion<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">v1<\/span>\n<span class=\"nt\">kind<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">Pod<\/span>\n<span class=\"nt\">metadata<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">test-bwrap<\/span>\n<span class=\"nt\">spec<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">containers<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">    <\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">test<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">image<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">quay.io\/zuul-ci\/zuul-executor<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">command<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p p-Indicator\">[<\/span><span class=\"s\">&quot;\/bin\/sleep&quot;<\/span><span class=\"p p-Indicator\">,<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;infinity&quot;<\/span><span class=\"p p-Indicator\">]<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">securityContext<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">capabilities<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">          <\/span><span class=\"nt\">add<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p p-Indicator\">[<\/span><span class=\"s\">&quot;SETFCAP&quot;<\/span><span class=\"p p-Indicator\">]<\/span>\n<\/pre><\/div>\n<!--  -->\n<blockquote>\n<p>As seen previously, we need <em>CAP_SETFCAP<\/em> to create the user\nnamespace, otherwise bwrap fails early with the following error:<\/p>\n<pre class=\"literal-block\">\nbwrap: setting up uid map: Operation not permitted\n<\/pre>\n<\/blockquote>\n<p>Apply the test resource with the following commands:<\/p>\n<div class=\"highlight\"><pre><span><\/span>$ export KUBECONFIG=\/var\/run\/kubernetes\/admin.kubeconfig\n$ kubectl apply -f test-bwrap.yaml\n$ kubectl exec test-bwrap -- bwrap --ro-bind \/lib \/lib --ro-bind \/usr \/usr --symlink \/usr\/lib64 \/lib64 --proc \/proc --dev \/dev --tmpfs \/tmp --unshare-all --new-session ps afx\nbwrap: Can&#39;t mount proc on \/newroot\/proc: Operation not permitted\n<\/pre><\/div>\n<p>This produces the same error we encountered in the <a class=\"reference external\" href=\"https:\/\/www.softwarefactory-project.io\/recursive-namespaces-to-run-containers-inside-a-container.html\">previous post<\/a>: the\n\/proc filesystem is tainted in the pod, preventing Bubblewrap from being\nable to create a new procfs for the new PID namespace.<\/p>\n<p>The next section introduces the <em>ProcMountType<\/em> feature to work around\nthis issue.<\/p>\n<\/div>\n<div class=\"section\" id=\"the-procmounttype-feature\">\n<h2>The ProcMountType feature<\/h2>\n<p>The <em>ProcMountType<\/em> feature can be enabled by adding the following\nenvironment variable to the <em>local-up-cluster<\/em>:\n<tt class=\"docutils literal\"><span class=\"pre\">FEATURE_GATES='UserNamespacesSupport=true,ProcMountType=true'<\/span><\/tt>. To\nmake use of the new feature, we also need to activate\n<em>UserNamespacesSupport<\/em>, as explained in the following <a class=\"reference external\" href=\"https:\/\/kubernetes.io\/docs\/tasks\/configure-pod-container\/security-context\/#proc-access\">documentation<\/a>.<\/p>\n<p>With these features, we can update the resource like that:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"nt\">apiVersion<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">v1<\/span>\n<span class=\"nt\">kind<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">Pod<\/span>\n<span class=\"nt\">metadata<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">test-bwrap<\/span>\n<span class=\"nt\">spec<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">hostUsers<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">false<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">containers<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">    <\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">test<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">image<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">quay.io\/zuul-ci\/zuul-executor<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">command<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p p-Indicator\">[<\/span><span class=\"s\">&quot;\/bin\/sleep&quot;<\/span><span class=\"p p-Indicator\">,<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;infinity&quot;<\/span><span class=\"p p-Indicator\">]<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">securityContext<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">procMount<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">Unmasked<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">capabilities<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">          <\/span><span class=\"nt\">add<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p p-Indicator\">[<\/span><span class=\"s\">&quot;SETFCAP&quot;<\/span><span class=\"p p-Indicator\">]<\/span>\n<\/pre><\/div>\n<p>\u2026 using the following commands:<\/p>\n<pre class=\"literal-block\">\n$ sudo crictl rm -af; kubectl delete -f .\/test-bwrap.yaml &amp;&amp; kubectl apply -f .\/test-bwrap.yaml\npod\/test-bwrap created\n$ kubectl exec test-bwrap -- bwrap --ro-bind \/lib \/lib --ro-bind \/usr \/usr --symlink \/usr\/lib64 \/lib64 --proc \/proc --dev \/dev --tmpfs \/tmp --unshare-all --new-session ps afx\nbwrap: Can't mount proc on \/newroot\/proc: Permission denied\n<\/pre>\n<p>This time we get a new permission denied, which is caused by SELinux.\nUsing <em>audit2allow<\/em>, we can see that the following policy needs to be\ninstalled:<\/p>\n<pre class=\"literal-block\">\nmodule nestedcontainers 1.0;\n\nrequire {\n    type proc_t;\n    type devpts_t;\n    type container_t;\n    class filesystem mount;\n}\n\n#============= container_t ==============\nallow container_t devpts_t:filesystem mount;\nallow container_t proc_t:filesystem mount;\n<\/pre>\n<p>\u2026 which lets us run Bubblewrap inside an unprivileged pod:<\/p>\n<div class=\"highlight\"><pre><span><\/span>$ sudo semodule -i nestedcontainers.pp\n$ kubectl exec test-bwrap -- bwrap --ro-bind \/lib \/lib --ro-bind \/usr \/usr --symlink \/usr\/lib64 \/lib64 --proc \/proc --dev \/dev --tmpfs \/tmp --unshare-all --new-session ps afx\n    PID TTY      STAT   TIME COMMAND\n      1 ?        Ss     0:00 bwrap --ro-bind \/lib \/lib --ro-bind \/usr \/usr --symlink \/usr\/lib64 \/lib64 --proc \/proc --dev \/dev --tmpfs \/tmp --unshare-all --new-session --cap-add all --uid 0 ps afx\n      2 ?        R      0:00 ps afx\n<\/pre><\/div>\n<p>Notice how the <tt class=\"docutils literal\">sleep infinity<\/tt> process is not visible in the ps\noutput, confirming that we are indeed running in a nested container.<\/p>\n<\/div>\n<div class=\"section\" id=\"conclusion\">\n<h2>Conclusion<\/h2>\n<p>This post demonstrates that we can run a container inside a container\nwith Kubernetes thanks to the following settings:<\/p>\n<ul class=\"simple\">\n<li>The SETFCAP to create the user namespace,<\/li>\n<li>The ProcMountType and UserNamespacesSupport to unmask the \/proc\nfilesystem, and<\/li>\n<li>A SELinux policy to enable mounting filesystems inside the new\nnamespace.<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2024 Oct 11 to 2024 Oct 30 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2024-oct-11-to-2024-oct-30-summary.html","rel":"alternate"}},"published":"2024-10-30T10:00:00+00:00","updated":"2024-10-30T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2024-10-30:\/sprint-2024-oct-11-to-2024-oct-30-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"software-factory-sf-operator\">\n<h2>Software Factory - sf-operator<\/h2>\n<ul class=\"simple\">\n<li>We migrated the www.softwarefactory-project.io website to GitHub pages<\/li>\n<li>we released sf-operator v.0.0.45 with several minor improvements to log forwarding<\/li>\n<li>We increased Zuul Scheduler and Web Probes<\/li>\n<li>We added admin-rules to internal \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"software-factory-sf-operator\">\n<h2>Software Factory - sf-operator<\/h2>\n<ul class=\"simple\">\n<li>We migrated the www.softwarefactory-project.io website to GitHub pages<\/li>\n<li>we released sf-operator v.0.0.45 with several minor improvements to log forwarding<\/li>\n<li>We increased Zuul Scheduler and Web Probes<\/li>\n<li>We added admin-rules to internal tenant<\/li>\n<li>We added Zuul Restart to command<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2024 Sep 19 to 2024 Oct 09 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2024-sep-19-to-2024-oct-09-summary.html","rel":"alternate"}},"published":"2024-10-09T10:00:00+00:00","updated":"2024-10-09T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2024-10-09:\/sprint-2024-sep-19-to-2024-oct-09-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>The opendev sysadmins continued testing of new raxflex offering<\/li>\n<li>The OpenStack community released 2024.2\/Dalmatian!<\/li>\n<li>The opendev sysadmins updated Etherpad service in new release.<\/li>\n<li>The opendev sysadmins updated Gitea service to lastes bugfix<\/li>\n<li>The opendev sysadmins updated \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>The opendev sysadmins continued testing of new raxflex offering<\/li>\n<li>The OpenStack community released 2024.2\/Dalmatian!<\/li>\n<li>The opendev sysadmins updated Etherpad service in new release.<\/li>\n<li>The opendev sysadmins updated Gitea service to lastes bugfix<\/li>\n<li>The opendev sysadmins updated all [zuul] tennants to Ansible 9<\/li>\n<li>The Zuul community made substatial progess made on the &quot;Nodepool-in-Zuul&quot; [niz] work.<\/li>\n<li>The opendev sysadmins added the new zuul-launcher service, part of the &quot;Nodepool-in-Zuul&quot; [niz] work.<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory-sf-operator\">\n<h2>Software Factory - sf-operator<\/h2>\n<ul class=\"simple\">\n<li>We released v0.0.44 of sf-operator. This implements log forwarding with fluent bit using the &quot;forward&quot; protocol.<\/li>\n<li>We added a support for extending zuul executor size <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/32349\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/32349<\/a> and releasing v0.0.43<\/li>\n<li>We bump Zuul and Nodepool containers because of the new update-ca-truct package version<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2024 Aug 30 to 2024 Sep 18 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2024-aug-30-to-2024-sep-18-summary.html","rel":"alternate"}},"published":"2024-09-18T10:00:00+00:00","updated":"2024-09-18T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2024-09-18:\/sprint-2024-aug-30-to-2024-sep-18-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We updated OpenSearch cluster to 2.15.0<\/li>\n<li>We have investigated an occasional busy loop condition in the zuul-scheduler<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory-sf-operator\">\n<h2>Software Factory - sf-operator<\/h2>\n<ul class=\"simple\">\n<li>We bumped zuul version to 11.1.0<\/li>\n<li>We fixed the zookeeper cert auto-gen issue<\/li>\n<li>We \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We updated OpenSearch cluster to 2.15.0<\/li>\n<li>We have investigated an occasional busy loop condition in the zuul-scheduler<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory-sf-operator\">\n<h2>Software Factory - sf-operator<\/h2>\n<ul class=\"simple\">\n<li>We bumped zuul version to 11.1.0<\/li>\n<li>We fixed the zookeeper cert auto-gen issue<\/li>\n<li>We have fixed the volume expend system when sts have multiple replicas<\/li>\n<li>We have released multiple versions of sf-operator<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2024 Aug 09 to 2024 Aug 28 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2024-aug-09-to-2024-aug-28-summary.html","rel":"alternate"}},"published":"2024-08-28T10:00:00+00:00","updated":"2024-08-28T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2024-08-28:\/sprint-2024-aug-09-to-2024-aug-28-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We added a url subpath to support opensearch endpoints that contains subdir, for example: localhost\/opensearch in ci-log-processing project<\/li>\n<li>We added a patch to pass the CI for OpenSearch 2.13 and OpenSearch Dashboards 2.13 in ci-log-processing \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We added a url subpath to support opensearch endpoints that contains subdir, for example: localhost\/opensearch in ci-log-processing project<\/li>\n<li>We added a patch to pass the CI for OpenSearch 2.13 and OpenSearch Dashboards 2.13 in ci-log-processing project<\/li>\n<li>We reviewed the zuul-operator fixes.<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We worked on integrating zuul-weeder and LogJuicer into the sf-operator<\/li>\n<li>We added dhall, dhall-json and \/var\/cache\/dhall on zuul-executor image for dhall-diff job<\/li>\n<li>We increased etcd ramdisk size to 1g because 512m was not enough<\/li>\n<li>We removed restartPolicy parameter for init containers becasue MicroShift 4.16 was complaing about it<\/li>\n<li>We tested a sf-operator with MicroShift 4.16 and we proposed a patch to move to that version as default (it is a LTS release)<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2024 Jul 19 to 2024 Aug 07 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2024-jul-19-to-2024-aug-07-summary.html","rel":"alternate"}},"published":"2024-08-07T10:00:00+00:00","updated":"2024-08-07T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2024-08-07:\/sprint-2024-jul-19-to-2024-aug-07-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We updated gitea to 1.22<\/li>\n<li>We resolved issues with meetpad failing to restart after (upstream) container update.<\/li>\n<li>We prepared and launched service nodes on Ubuntu Noble<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We integrated Zuul version 11.0.1: <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/q\/topic:zuul-v11.0.1\">https:\/\/softwarefactory-project \u2026<\/a><\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We updated gitea to 1.22<\/li>\n<li>We resolved issues with meetpad failing to restart after (upstream) container update.<\/li>\n<li>We prepared and launched service nodes on Ubuntu Noble<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We integrated Zuul version 11.0.1: <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/q\/topic:zuul-v11.0.1\">https:\/\/softwarefactory-project.io\/r\/q\/topic:zuul-v11.0.1<\/a><\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2024 Jun 28 to 2024 Jul 17 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2024-jun-28-to-2024-jul-17-summary.html","rel":"alternate"}},"published":"2024-07-17T10:00:00+00:00","updated":"2024-07-17T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2024-07-17:\/sprint-2024-jun-28-to-2024-jul-17-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We reviewed the security fix for Zuul: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/923874\">https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/923874<\/a><\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We released zuul 11.0.0 on rhel-9<\/li>\n<li>We fixed the logging for Zuul <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-config\/+\/31885\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-config \u2026<\/a><\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We reviewed the security fix for Zuul: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/923874\">https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/923874<\/a><\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We released zuul 11.0.0 on rhel-9<\/li>\n<li>We fixed the logging for Zuul <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-config\/+\/31885\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-config\/+\/31885<\/a><\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"sf-operator\">\n<h2>Sf-operator<\/h2>\n<ul class=\"simple\">\n<li>We updated the deployment to v0.0.33 with Zuul 11.0.0<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2024 Jun 07 to 2024 Jun 26 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2024-jun-07-to-2024-jun-26-summary.html","rel":"alternate"}},"published":"2024-06-26T10:00:00+00:00","updated":"2024-06-26T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2024-06-26:\/sprint-2024-jun-07-to-2024-jun-26-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We removed old InMotion cloud, and replaced it with newer version of OpenMetal<\/li>\n<li>We fixed the Gitlab driver close action <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/921855\">https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/921855<\/a><\/li>\n<li>We fixed the Gitlab autohold <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/910942\">https:\/\/review.opendev.org\/c \u2026<\/a><\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We removed old InMotion cloud, and replaced it with newer version of OpenMetal<\/li>\n<li>We fixed the Gitlab driver close action <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/921855\">https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/921855<\/a><\/li>\n<li>We fixed the Gitlab autohold <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/910942\">https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/910942<\/a><\/li>\n<li>We updated the patch for zuul-client enabling the creation of an auth token automatically if the right config is provided<\/li>\n<li>We finished the re-ansi upgrade to rescript: <a class=\"reference external\" href=\"https:\/\/forum.rescript-lang.org\/t\/notes-about-migrating-from-bs-platform-8-2-to-rescript-11\/5314\">https:\/\/forum.rescript-lang.org\/t\/notes-about-migrating-from-bs-platform-8-2-to-rescript-11\/5314<\/a><\/li>\n<li>We fixed an issue with centos build artifacts: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/921689\">https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/921689<\/a><\/li>\n<li>We fixed the zuul-jobs third-party-ci<\/li>\n<li>We reviewed the zuul specs and elasticsearch thread<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We updated the sf-3.8 release repo with the latest sf-config<\/li>\n<li>We added support for implicit semaphore to zuul-weeder <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/zuul-weeder\/+\/31734\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/zuul-weeder\/+\/31734<\/a><\/li>\n<li>We dropped opensearch and influxdb stacks for allinone arch on rhel-9<\/li>\n<li>We added sf-ci-functional-allinone and sf-tenants jobs for sf-ci on rhel-9<\/li>\n<li>we backported the 3.8 branch to master <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-config\/+\/31688\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-config\/+\/31688<\/a><\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"sf-operator\">\n<h2>Sf-operator<\/h2>\n<ul class=\"simple\">\n<li>We released sf-operator v0.0.31 for the landing of the new MariaDB version<\/li>\n<li>We added a better handling of logging with a support of debug loglevel <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/31674\/\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/31674\/<\/a><\/li>\n<li>We removed the Route\/LE support <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/31452\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/31452<\/a><\/li>\n<li>We updated the operator images sec issues Critical and Important + Zuul upstream patch <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/31700\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/31700<\/a><\/li>\n<li>We updated zuul-client working on zuul pods (need to merge <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/containers\/+\/31741\">https:\/\/softwarefactory-project.io\/r\/c\/containers\/+\/31741<\/a> and land a sf-operator patch)<\/li>\n<li>We fixed issue with the cert after moving to the Microshift 4.14<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2024 May 16 to 2024 Jun 05 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2024-may-16-to-2024-jun-05-summary.html","rel":"alternate"}},"published":"2024-06-05T10:00:00+00:00","updated":"2024-06-05T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2024-06-05:\/sprint-2024-may-16-to-2024-jun-05-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We reviewed the status page v2 spec: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/918505\">https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/918505<\/a><\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We reviewed the sf-operator resource limit addition<\/li>\n<li>We implemented small improvements for great speedup in sf-config by combining executor\/merger host \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We reviewed the status page v2 spec: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/918505\">https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/918505<\/a><\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We reviewed the sf-operator resource limit addition<\/li>\n<li>We implemented small improvements for great speedup in sf-config by combining executor\/merger host and by implementing a container warmup playbook: <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-config\/+\/31612\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-config\/+\/31612<\/a><\/li>\n<li>We fixed a &quot;nasty&quot; issue where deserializing YAML data might be inconsistent over time, resulting in reconcile errors.<\/li>\n<li>We updated MariaDB container image to 10.6<\/li>\n<li>We added a proposal to update cert manager to new version<\/li>\n<li>We fixed the upgrade-12h periodic pipeline <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/sf-zuul-jobs-config\/+\/31527\">https:\/\/softwarefactory-project.io\/r\/c\/sf-zuul-jobs-config\/+\/31527<\/a><\/li>\n<li>We landed the change of the container limits <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/31462\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/31462<\/a><\/li>\n<li>We landed the change to move the &quot;sf wipe&quot; cli command to a &quot;dev&quot; command <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/31259\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/31259<\/a><\/li>\n<li>We managed to fixed multiple sources of flakyness to bring back the CI in better shape<\/li>\n<li>We updated Zuul container image to 10.1.0 version<\/li>\n<li>We updated doc for sf-operator<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2024 Apr 26 to 2024 May 15 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2024-apr-26-to-2024-may-15-summary.html","rel":"alternate"}},"published":"2024-05-15T10:00:00+00:00","updated":"2024-05-15T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2024-05-15:\/sprint-2024-apr-26-to-2024-may-15-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We reviewed the new zuul status page <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/q\/topic:%22fe-status-view-2.0%22\">https:\/\/review.opendev.org\/q\/topic:%22fe-status-view-2.0%22<\/a><\/li>\n<li>We bumped re-ansi to the latest rescript toolchain<\/li>\n<li>We updated servers for jitsi_meet from Bionic to Jammy<\/li>\n<li>We infra team also updated \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We reviewed the new zuul status page <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/q\/topic:%22fe-status-view-2.0%22\">https:\/\/review.opendev.org\/q\/topic:%22fe-status-view-2.0%22<\/a><\/li>\n<li>We bumped re-ansi to the latest rescript toolchain<\/li>\n<li>We updated servers for jitsi_meet from Bionic to Jammy<\/li>\n<li>We infra team also updated etherpad from 1.x to 2.x, all self hosted MariaDBs to 10.11 and several rebuilds of gerrit<\/li>\n<li>We configure logscraper02 host on OpenDev that will replace logscraper01<\/li>\n<li>We updated (in progress) OpenSearch on Opendev to 2.11<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We added rhel9 support to sf-config<\/li>\n<li>We have proposed a solution for upgrade Zuul and Nodepool on version 10.0.0 for sf 3.8 <a class=\"reference external\" href=\"https:\/\/www.softwarefactory-project.io\/software-factory-38-zuulnodepool-update.html\">https:\/\/www.softwarefactory-project.io\/software-factory-38-zuulnodepool-update.html<\/a><\/li>\n<li>We proposed a change to set container limites <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/31462\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/31462<\/a><\/li>\n<li>We added the sub command to work with zuul-scheduler<\/li>\n<li>We start to work on the sub command to query zookeeper queues and nodes<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Software Factory 3.8 Zuul\/Nodepool Update","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/software-factory-38-zuulnodepool-update.html","rel":"alternate"}},"published":"2024-05-02T00:00:00+00:00","updated":"2024-05-02T00:00:00+00:00","author":{"name":"fbo"},"id":"tag:www.softwarefactory-project.io,2024-05-02:\/software-factory-38-zuulnodepool-update.html","summary":"<p>Software Factory 3.8 is featured with Zuul and Nodepool 8.1 and as of today we have not scheduled to\nrelease a new official Software Factory version as our goal is to migrate to an OpenShift based deployment\nthrough the <a class=\"reference external\" href=\"https:\/\/github.com\/softwarefactory-project\/sf-operator\">sf-operator<\/a> project.<\/p>\n<p>However to mitigate the delay to migrate \u2026<\/p>","content":"<p>Software Factory 3.8 is featured with Zuul and Nodepool 8.1 and as of today we have not scheduled to\nrelease a new official Software Factory version as our goal is to migrate to an OpenShift based deployment\nthrough the <a class=\"reference external\" href=\"https:\/\/github.com\/softwarefactory-project\/sf-operator\">sf-operator<\/a> project.<\/p>\n<p>However to mitigate the delay to migrate our production to the sf-operator we are providing a solution\nto enable Zuul 10.0.0 and Nodepool 10.0.0 with the current Software Factory 3.8.<\/p>\n<p>Assuming the Software Factory deployment is running the version 3.8 (sf-config-3.8.8-4), we can follow\nthe process below.<\/p>\n<div class=\"admonition note\">\n<p class=\"first admonition-title\">Note<\/p>\n<p class=\"last\">Make sure to have a minimum of 10GB disk space available on all the hosts part of your SF infra to perform the update.<\/p>\n<\/div>\n<div class=\"highlight\"><pre><span><\/span><span class=\"c1\"># Backup your Software Factory data<\/span>\nansible-playbook<span class=\"w\"> <\/span>\/var\/lib\/software-factory\/ansible\/sf_backup.yml\n\n<span class=\"c1\"># Install this specific sf-config package<\/span>\nyum<span class=\"w\"> <\/span>install<span class=\"w\"> <\/span>sf-config-3.8.9-4\n\n<span class=\"c1\"># Ensure to deactivate the automatic restart of Zuul and Nodepool<\/span>\n<span class=\"nb\">echo<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;disable_zuul_autorestart: true&quot;<\/span><span class=\"w\"> <\/span>&gt;&gt;<span class=\"w\"> <\/span>\/etc\/software-factory\/custom-vars.yaml\n<span class=\"nb\">echo<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;disable_nodepool_autorestart: true&quot;<\/span><span class=\"w\"> <\/span>&gt;&gt;<span class=\"w\"> <\/span>\/etc\/software-factory\/custom-vars.yaml\n\n<span class=\"c1\"># Stop all Zuul\/Nodepool services (adapt the command if services are running on multiple nodes)<\/span>\nsystemctl<span class=\"w\"> <\/span>stop<span class=\"w\"> <\/span>zuul-scheduler<span class=\"w\"> <\/span>zuul-merger<span class=\"w\"> <\/span>zuul-web<span class=\"w\"> <\/span>zuul-executor<span class=\"w\"> <\/span>zuul-fingergw<span class=\"w\"> <\/span><span class=\"se\">\\<\/span>\n<span class=\"w\"> <\/span>nodepool-launcher<span class=\"w\"> <\/span>nodepool-builder\n\n<span class=\"c1\"># A Zuul SQL database migration need to be performed by Zuul at startup and we have noticed<\/span>\n<span class=\"c1\"># an issue with our DBs content that prevent the migration to success. To fix it, run:<\/span>\npodman<span class=\"w\"> <\/span><span class=\"nb\">exec<\/span><span class=\"w\"> <\/span>-it<span class=\"w\"> <\/span>mysql<span class=\"w\"> <\/span>bash<span class=\"w\"> <\/span>-c<span class=\"w\"> <\/span><span class=\"s1\">&#39;mysql -uroot -p$MYSQL_ROOT_PASSWORD zuul -e &quot;delete from zuul_buildset where (CHAR_LENGTH(oldrev) &gt; 40 OR CHAR_LENGTH(newrev) &gt; 40 OR CHAR_LENGTH(patchset) &gt; 40);&quot;&#39;<\/span>\n\n<span class=\"c1\"># Delete the Zuul Ephemeral state from Zookeeper<\/span>\nzuul_wrapper<span class=\"w\"> <\/span>delete-state\n\n<span class=\"c1\"># Then run the sf-config upgrade command<\/span>\nsf-config<span class=\"w\"> <\/span>--upgrade\n\n<span class=\"c1\"># Re-create the zuul-scheduler container based on the updated container image<\/span>\npodman<span class=\"w\"> <\/span>rm<span class=\"w\"> <\/span>zuul-scheduler<span class=\"p\">;<\/span><span class=\"w\"> <\/span>\/usr\/local\/bin\/container-zuul-scheduler.sh<span class=\"p\">;<\/span><span class=\"w\"> <\/span>rm<span class=\"w\"> <\/span>\/var\/lib\/software-factory\/versions\/zuul-scheduler-updated\n\n<span class=\"c1\"># Now start the zuul-scheduler and ensure that the database migration has been performed without issue.<\/span>\nsystemctl<span class=\"w\"> <\/span>start<span class=\"w\"> <\/span>zuul-scheduler\ntail<span class=\"w\"> <\/span>-f<span class=\"w\"> <\/span>\/var\/log\/zuul\/scheduler.log\n\n<span class=\"c1\"># Perform a full restart of Zuul<\/span>\nansible-playbook<span class=\"w\"> <\/span>\/var\/lib\/software-factory\/ansible\/zuul_restart.yml\n\n<span class=\"c1\"># Then restart all Nodepool services<\/span>\nansible-playbook<span class=\"w\"> <\/span>\/var\/lib\/software-factory\/ansible\/nodepool_restart.yml\n<\/pre><\/div>\n<p>For more information about the Zuul SQL migration please refer to\nthe <cite>Zuul changelog &lt;https:\/\/zuul-ci.org\/docs\/zuul\/latest\/releasenotes.html#relnotes-9-3-0-upgrade-notes&gt;<\/cite>.<\/p>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2024 Apr 05 to 2024 Apr 24 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2024-apr-05-to-2024-apr-24-summary.html","rel":"alternate"}},"published":"2024-04-24T10:00:00+00:00","updated":"2024-04-24T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2024-04-24:\/sprint-2024-apr-05-to-2024-apr-24-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We changed the OpenSearch version for testing the ci-log-processing project, due in the near future, we will update the logscraper host and by taking the opportunity, we will also trigger OpenSearch update + we scheduled upgrade date<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory \u2026<\/h2><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We changed the OpenSearch version for testing the ci-log-processing project, due in the near future, we will update the logscraper host and by taking the opportunity, we will also trigger OpenSearch update + we scheduled upgrade date<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We are working on sf-3.8 to provide Zuul and Nodepool 10.0<\/li>\n<li>We validated SF 3.8 (minimal arch) can be deployed on rhel-9, the follow up will be to enable ci testing, but there is no migration path from centos-7 to rhel-9, backup\/restore from centos-7 deployment will be needed<\/li>\n<li>We finialised the Backup\/Restore system<\/li>\n<li>We bumped controller-runtime lib deps to latest <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/31266\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/31266<\/a><\/li>\n<li>We proposed Proposed a cli command to get an insight of HIGH and CRITICAL issue on our sf-operator container images <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/31283\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/31283<\/a><\/li>\n<li>We bumped the zookeeper image (report HIGH Sec issues)<\/li>\n<li>We investigated how to enable SSO on top SF's web services<\/li>\n<li>We updated doc for sf-operator<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2024 Mar 15 to 2024 Apr 03 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2024-mar-15-to-2024-apr-03-summary.html","rel":"alternate"}},"published":"2024-04-03T10:00:00+00:00","updated":"2024-04-03T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2024-04-03:\/sprint-2024-mar-15-to-2024-apr-03-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We reviewed the new openapi spec generator for Zuul: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/912031\">https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/912031<\/a><\/li>\n<li>We investigated how latest docker broke zuul-jobs in <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul-jobs\/+\/913808\">https:\/\/review.opendev.org\/c\/zuul\/zuul-jobs\/+\/913808<\/a><\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We bumped zuul \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We reviewed the new openapi spec generator for Zuul: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/912031\">https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/912031<\/a><\/li>\n<li>We investigated how latest docker broke zuul-jobs in <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul-jobs\/+\/913808\">https:\/\/review.opendev.org\/c\/zuul\/zuul-jobs\/+\/913808<\/a><\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We bumped zuul and nodepool to 10.0.0<\/li>\n<li>We tagged sf-operator v0.0.27<\/li>\n<li>We added restore functionality; the functional tests and other related to it are WIP<\/li>\n<li>We implemented the external executor and fonctional tests<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2024 Feb 23 to 2024 Mar 13 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2024-feb-23-to-2024-mar-13-summary.html","rel":"alternate"}},"published":"2024-03-13T10:00:00+00:00","updated":"2024-03-13T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2024-03-13:\/sprint-2024-feb-23-to-2024-mar-13-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We removed log  gearman - {client, worker}  in ci-log-processing project in logscraper and logsender due Opendev folks are not using it anymore (same as we)<\/li>\n<li>We fixed parsing fields in logsender after Opendev migrate to new Zuul<\/li>\n<li>We proposed \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We removed log  gearman - {client, worker}  in ci-log-processing project in logscraper and logsender due Opendev folks are not using it anymore (same as we)<\/li>\n<li>We fixed parsing fields in logsender after Opendev migrate to new Zuul<\/li>\n<li>We proposed to change the container base image to Centos 9 stream<\/li>\n<li>We pinned the python env version to 3.11 to avoid future issues with Python code<\/li>\n<li>We proposed a fix on zuul to support autoholds on changes hosted on gitlab (still wip, I'd like to test it on real data)<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We Fixed gitlab urls for weeder: <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/zuul-weeder\/+\/31049\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/zuul-weeder\/+\/31049<\/a><\/li>\n<li>We have applied the ansible_fqcn_fixer.sh script to some of our repositories to ensure the use of fully qualified module names.<\/li>\n<li>We proposed a restore functionality in sf-operator<\/li>\n<li>We proposed a dedicated new job in sf-operator to verify environment after the disaster (verifying backup and restore functionality)<\/li>\n<li>We did an implementation of external zuul-executor according to the ADR14 of sf-operator:<ul>\n<li><a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30956\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30956<\/a><\/li>\n<li><a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/31047\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/31047<\/a><\/li>\n<li><a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/31057\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/31057<\/a><\/li>\n<\/ul>\n<\/li>\n<li>We updated some MD files with file paths instead of link: <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30831\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30831<\/a><\/li>\n<li>We prepared the Blog post to announce the release of Software Factory Operator: <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/www.softwarefactory-project.io\/+\/30847\">https:\/\/softwarefactory-project.io\/r\/c\/www.softwarefactory-project.io\/+\/30847<\/a><\/li>\n<li>We added a Github connection on microshift.softwarefactory.io: <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-infra\/+\/30969\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-infra\/+\/30969<\/a><\/li>\n<li>We started to work on how to migrate Zuul secrets from one Zuul to another with different key:<ul>\n<li><a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/31043\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/31043<\/a><\/li>\n<li><a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/908507\">https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/908507<\/a><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2024 Feb 02 to 2024 Feb 21 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2024-feb-02-to-2024-feb-21-summary.html","rel":"alternate"}},"published":"2024-02-21T10:00:00+00:00","updated":"2024-02-21T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2024-02-21:\/sprint-2024-feb-02-to-2024-feb-21-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We reviewed a couple of change made to Zuul, notably fixes for google\/pytype<\/li>\n<li>We tried to save some compute resources in periodic pipeline: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul-jobs\/+\/827369\">https:\/\/review.opendev.org\/c\/zuul\/zuul-jobs\/+\/827369<\/a><\/li>\n<li>We fixed support for bright colors \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We reviewed a couple of change made to Zuul, notably fixes for google\/pytype<\/li>\n<li>We tried to save some compute resources in periodic pipeline: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul-jobs\/+\/827369\">https:\/\/review.opendev.org\/c\/zuul\/zuul-jobs\/+\/827369<\/a><\/li>\n<li>We fixed support for bright colors in re-ansi and bump zuul-web dependencies<\/li>\n<li>We added a feature for logscraper and logsender that would be passible to pass multiple yaml files (filrs that should be downloaded)<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We switched doc dev\/publishing to material for mkdocs AND IT IS LIVE! <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.github.io\/sf-operator\/\">https:\/\/softwarefactory-project.github.io\/sf-operator\/<\/a><\/li>\n<li>We added a backup feature; restore feature is in progress<\/li>\n<li>We added the default Logserver storageclass definition for CR storage value<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Recursive namespaces to run containers inside a container","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/recursive-namespaces-to-run-containers-inside-a-container.html","rel":"alternate"}},"published":"2024-02-19T00:00:00+00:00","updated":"2024-02-19T00:00:00+00:00","author":{"name":"tristanC"},"id":"tag:www.softwarefactory-project.io,2024-02-19:\/recursive-namespaces-to-run-containers-inside-a-container.html","summary":"<style type=\"text\/css\">\n\n  .literal {\n    border-radius: 6px;\n    padding: 1px 1px;\n    background-color: rgba(27,31,35,.05);\n  }\n\n<\/style><p>We would like to deploy a containerized workload that creates nested\ncontainers to isolate individual tasks. This post explores the\nchallenges of safely running a container inside a container. In three\nparts, I present:<\/p>\n<ul class=\"simple\">\n<li>User namespaces.<\/li>\n<li>Required capabilities \u2026<\/li><\/ul>","content":"<style type=\"text\/css\">\n\n  .literal {\n    border-radius: 6px;\n    padding: 1px 1px;\n    background-color: rgba(27,31,35,.05);\n  }\n\n<\/style><p>We would like to deploy a containerized workload that creates nested\ncontainers to isolate individual tasks. This post explores the\nchallenges of safely running a container inside a container. In three\nparts, I present:<\/p>\n<ul class=\"simple\">\n<li>User namespaces.<\/li>\n<li>Required capabilities.<\/li>\n<li>Procfs kernel restrictions.<\/li>\n<\/ul>\n<!--  -->\n<blockquote>\n<p>The examples in this post are using the following packages:<\/p>\n<ul class=\"simple\">\n<li>kernel-6.6.11-200.fc39.x86_64<\/li>\n<li>selinux-policy-39.3-1.fc39.noarch<\/li>\n<li>util-linux-core-2.39.3-1.fc39.x86_64<\/li>\n<li>bubblewrap-0.8.0-1.fc39.x86_64<\/li>\n<li>podman-4.8.3-1.fc39.x86_64<\/li>\n<\/ul>\n<\/blockquote>\n<div class=\"section\" id=\"context-and-problem-statement\">\n<h2>Context and problem statement<\/h2>\n<p>The context is leveraging the <a class=\"reference external\" href=\"https:\/\/github.com\/containers\/bubblewrap\">bubblewrap<\/a> tool to create temporary\nsandboxes for running Ansible playbooks as part of a CI build system\nnamed <tt class=\"docutils literal\"><span class=\"pre\">zuul-executor<\/span><\/tt>.<\/p>\n<p>The problem we are facing is that creating nested containers requires a\nprivileged context from the parent container runtime. And this is an\nissue when running in an environment that enforces security constraints,\nlike OpenShift clusters managed by a third party.<\/p>\n<p>The next sections describe the implications of this privileged context.<\/p>\n<\/div>\n<div class=\"section\" id=\"user-namespaces\">\n<h2>User namespaces<\/h2>\n<p>Since RHEL8, regular users are allowed to create namespaces. This used\nto be a privileged action that only the admin (root) could perform. But\nthanks to the unprivileged user namespace, users can become root in a\nlimited context to perform the actions required to setup a container.<\/p>\n<p>We can explore this feature using the standard <tt class=\"docutils literal\">unshare<\/tt> utility. As a\nregular user, we can create new namespaces that are isolated from the\nhost:<\/p>\n<div class=\"highlight\"><pre><span><\/span>[tristanc@fedora ~]$ unshare --user --mount --net --pid --fork --map-root-user --mount-proc\nroot@fedora:~# id\nuid=0(root) gid=65534(nfsnobody) groups=65534(nfsnobody) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023\nroot@fedora:~# ip a\n1: lo: &lt;LOOPBACK&gt; mtu 65536 qdisc noop state DOWN group default qlen 1000\n    link\/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00\nroot@fedora:~# ps afx\n    PID TTY      STAT   TIME COMMAND\n      1 pts\/5    S      0:00 -bash\n     79 pts\/5    R+     0:00 ps afx\n<\/pre><\/div>\n<p>Above we can see that:<\/p>\n<ul class=\"simple\">\n<li><tt class=\"docutils literal\"><span class=\"pre\">--user<\/span><\/tt> creates a new uid mapping which lets us become root.<\/li>\n<li><tt class=\"docutils literal\"><span class=\"pre\">--net<\/span><\/tt> creates a new network stack.<\/li>\n<li><tt class=\"docutils literal\"><span class=\"pre\">--pid<\/span><\/tt> creates a new procfs.<\/li>\n<\/ul>\n<p>To create these namespaces, the process uses the\n<tt class=\"docutils literal\">CLONE_NEWNS|CLONE_NEWUSER|CLONE_NEWPID|CLONE_NEWNET<\/tt> flags (either\nfor the <tt class=\"docutils literal\">unshare(2)<\/tt> or <tt class=\"docutils literal\">clone(2)<\/tt> syscall).<\/p>\n<p>Note that it is necessary to create a new user namespace (with\n<tt class=\"docutils literal\"><span class=\"pre\">--user<\/span><\/tt>), otherwise we wouldn't get the capabilities for creating the\nother namespaces.<\/p>\n<p>We can also create nested namespaces:<\/p>\n<div class=\"highlight\"><pre><span><\/span>[tristanc@fedora ~]$ unshare --user --mount --net --pid --fork --map-root-user --mount-proc\nroot@fedora:~# sleep 1001 &amp;\n[1] 23\nroot@fedora:~# unshare --user --mount --net --pid --fork --map-root-user --mount-proc\nroot@fedora:~# ps afx\n    PID TTY      STAT   TIME COMMAND\n      1 pts\/8    S      0:00 -bash\n     23 pts\/8    R+     0:00 ps afx\nroot@fedora:~# exit\nroot@fedora:~# ps afx\n    PID TTY      STAT   TIME COMMAND\n      1 pts\/8    S      0:00 -bash\n     23 pts\/8    S      0:00 sleep 1001\n     48 pts\/8    R+     0:00 ps afx\n<\/pre><\/div>\n<p>We can also use the <tt class=\"docutils literal\">bwrap<\/tt> command from the bubblewrap package to\nachieve the same kind of isolation:<\/p>\n<div class=\"highlight\"><pre><span><\/span>[tristanc@fedora ~]$ bwrap --ro-bind \/usr \/usr --symlink usr\/lib64 \/lib64 --proc \/proc --dev \/dev --tmpfs \/tmp --unshare-all --new-session --cap-add all --uid 0 bash\nbash: cannot set terminal process group (1): Inappropriate ioctl for device\nbash: no job control in this shell\nbash-5.2# sleep 4242 &amp;\n[1] 7\nbash-5.2# bwrap --ro-bind \/usr \/usr --symlink usr\/lib64 \/lib64 --proc \/proc --dev \/dev --tmpfs \/tmp --unshare-all --new-session --cap-add all --uid 0 bash\nbash: cannot set terminal process group (1): Inappropriate ioctl for device\nbash: no job control in this shell\nbash-5.2# ps afx\n    PID TTY      STAT   TIME COMMAND\n      1 ?        Ss     0:00 bwrap --ro-bind \/usr \/usr --symlink usr\/lib64 \/lib64 --proc \/proc --dev \/dev --tmpfs \/tmp --unshare-all --new-session --cap-add all --uid 0 bash\n      2 ?        S      0:00 bash\n      3 ?        R      0:00  \\_ ps afx\n<\/pre><\/div>\n<p>And we can confirm from the host that the namespaces are indeed nested:<\/p>\n<div class=\"highlight\"><pre><span><\/span>[tristanc@fedora ~]$ ps afx\n...\n 165104 pts\/8    Ss     0:00  |   \\_ \/bin\/bash --posix\n 170707 pts\/8    S+     0:00  |       \\_ bwrap --ro-bind \/usr \/usr --symlink usr\/lib64 \/lib64 --proc \/proc --dev \/dev --tmpfs \/tmp --unshare-all --new-session --cap-add all --uid 0 bash\n 170708 ?        Ss     0:00  |           \\_ bwrap --ro-bind \/usr \/usr --symlink usr\/lib64 \/lib64 --proc \/proc --dev \/dev --tmpfs \/tmp --unshare-all --new-session --cap-add all --uid 0 bash\n 170709 ?        S      0:00  |               \\_ bash\n 170826 ?        S      0:00  |                   \\_ sleep 4242\n 170827 ?        S      0:00  |                   \\_ bwrap --ro-bind \/usr \/usr --symlink usr\/lib64 \/lib64 --proc \/proc --dev \/dev --tmpfs \/tmp --unshare-all --new-session --cap-add all --uid 0 bash\n 170828 ?        Ss     0:00  |                       \\_ bwrap --ro-bind \/usr \/usr --symlink usr\/lib64 \/lib64 --proc \/proc --dev \/dev --tmpfs \/tmp --unshare-all --new-session --cap-add all --uid 0 bash\n 170829 ?        S      0:00  |                           \\_ bash\n<\/pre><\/div>\n<p>In this section, we demonstrated that a regular unprivileged user is\nable to create namespaces recursively (up to 32 layers). And even though\nthe user appears to be root in the namespace, it is still a regular user\nfrom the host perspective, and the user didn't gain new privileges.<\/p>\n<p>In the next section, we investigate what happens when the first\nnamespace is created by a container runtime.<\/p>\n<\/div>\n<div class=\"section\" id=\"container-runtime\">\n<h2>Container runtime<\/h2>\n<p>In a production environment, the initial container namespaces are\ncreated by a container runtime such as <a class=\"reference external\" href=\"https:\/\/github.com\/containers\/podman\">podman<\/a>. To investigate this\nsetup, let's add some tools to the fedora's base container image:<\/p>\n<pre class=\"literal-block\">\n[tristanc&#64;fedora ~]$ CTX=$(buildah from fedora)\n[tristanc&#64;fedora ~]$ buildah run $CTX dnf install -y util-linux procps-ng bubblewrap\n[tristanc&#64;fedora ~]$ buildah commit --rm $CTX fedora\n<\/pre>\n<p>With a minimal container, using the least amount of privileges by adding\n<tt class=\"docutils literal\"><span class=\"pre\">--cap-drop<\/span> all<\/tt>, we are not able to create the user namespace:<\/p>\n<pre class=\"literal-block\">\n[tristanc&#64;fedora ~]$ podman run --cap-drop all -it --rm fedora unshare --user --mount --net --pid --fork --map-root-user --mount-proc\nunshare: write failed \/proc\/self\/uid_map: Operation not permitted\n<\/pre>\n<p>At least, we need the <tt class=\"docutils literal\">setfcap<\/tt> capability which is enabled by\ndefault, but that is not enough:<\/p>\n<pre class=\"literal-block\">\n[tristanc&#64;fedora ~]$ podman run -it --rm fedora unshare --user --mount --net --pid --fork --map-root-user --mount-proc\nunshare: mount \/proc failed: Permission denied\n<\/pre>\n<p>It appears that we need to provide the <tt class=\"docutils literal\"><span class=\"pre\">--privileged<\/span><\/tt> flag:<\/p>\n<pre class=\"literal-block\">\n[tristanc&#64;fedora ~]$ podman run --privileged -it --rm fedora unshare --user --mount --net --pid --fork --map-root-user --mount-proc\n-sh-5.2# unshare --user --mount --net --pid --fork --map-root-user --mount-proc\n-sh-5.2#\n<\/pre>\n<p>Podman, as well as <a class=\"reference external\" href=\"https:\/\/github.com\/cri-o\/cri-o\">cri-o<\/a>, provides additional isolations. In the next\nsection we'll investigate what is happening.<\/p>\n<\/div>\n<div class=\"section\" id=\"procfs-kernel-restrictions\">\n<h2>Procfs kernel restrictions<\/h2>\n<p>It appears that, for the purpose of nested containerization, the\n<tt class=\"docutils literal\"><span class=\"pre\">--privileged<\/span><\/tt> argument keeps the <tt class=\"docutils literal\">\/proc<\/tt> untainted from any\nmountpoints. Indeed, we can observe that a regular container does not\nhave access to the full <tt class=\"docutils literal\">\/proc<\/tt>:<\/p>\n<pre class=\"literal-block\">\n[tristanc&#64;fedora ~]$ podman run -it --rm fedora grep &quot;^tmpfs \/proc&quot; \/proc\/mounts\ntmpfs \/proc\/acpi tmpfs ro,context=&quot;system_u:object_r:container_file_t:s0:c373,c905&quot;,relatime,size=0k,uid=1000,gid=1000,inode64 0 0\ntmpfs \/proc\/scsi tmpfs ro,context=&quot;system_u:object_r:container_file_t:s0:c373,c905&quot;,relatime,size=0k,uid=1000,gid=1000,inode64 0 0\n[tristanc&#64;fedora ~]$ podman run --privileged -it --rm fedora grep &quot;^tmpfs \/proc&quot; \/proc\/mounts | wc -l\n0\n<\/pre>\n<p>The container runtime hides some <tt class=\"docutils literal\">\/proc<\/tt> sub directories to prevent\nleaking unnecessary information from the host. We can observe the same\nbehavior without a container runtime, similar to what we did in the\nfirst section. For example the initial example no longer works in that\nsituation:<\/p>\n<pre class=\"literal-block\">\n[tristanc&#64;fedora ~]$ sudo mount -t tmpfs none \/proc\/scsi\n[sudo] password for tristanc:\n[tristanc&#64;fedora ~]$ unshare --user --mount --net --pid --fork --map-root-user --mount-proc\nunshare: mount \/proc failed: Operation not permitted\n[tristanc&#64;fedora ~]$ bwrap --ro-bind \/usr \/usr --symlink usr\/lib64 \/lib64 --proc \/proc --dev \/dev --tmpfs \/tmp --unshare-all --new-session --cap-add all --uid 0 ps afx\nbwrap: Can't mount proc on \/newroot\/proc: Operation not permitted\n<\/pre>\n<p>The same error can happen inside a privileged pod when manually hiding a\ndirectory, here <tt class=\"docutils literal\">\/proc\/scsi<\/tt>:<\/p>\n<pre class=\"literal-block\">\n[tristanc&#64;fedora ~]$ podman run --tmpfs \/proc\/scsi --privileged -it --rm fedora unshare --user --mount --net --pid --fork --map-root-user --mount-proc\nunshare: mount \/proc failed: Operation not permitted\n<\/pre>\n<p>When the procfs is not fully visible, then the kernel prevents further\nattempt to create a new fresh procfs, resulting in the\n<tt class=\"docutils literal\">mount \/proc failed: Operation not permitted<\/tt> error. This is\nunfortunate because our workload does not need a fully visible procfs,\nand the workload would work if the hidden paths were propagated\nautomatically. This is also confusing because the process is allowed to\ncreate the pid namespace with <tt class=\"docutils literal\">CLONE_NEWPID<\/tt>, but it is not allowed to\nuse it when mounting the procfs.<\/p>\n<p>Thankfully, as pointed out by &#64;giuseppe from the Red Hat Container Team,\nthere is already a <a class=\"reference external\" href=\"https:\/\/github.com\/kubernetes\/enhancements\/issues\/4265\">MountProc<\/a> enhancement proposed in kubernetes to\nenable this use-case.<\/p>\n<\/div>\n<div class=\"section\" id=\"conclusion\">\n<h2>Conclusion<\/h2>\n<p>In conclusion, we saw that creating recursive namespaces is possible\nunder normal conditions. However, container runtimes are tainting the\n<tt class=\"docutils literal\">\/proc<\/tt> file-system with tmpfs to prevent data from being exposed into\na container, and this alone prevents the creation of nested PID\nnamespace.<\/p>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2024 Jan 12 to 2024 Jan 31 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2024-jan-12-to-2024-jan-31-summary.html","rel":"alternate"}},"published":"2024-01-31T10:00:00+00:00","updated":"2024-01-31T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2024-01-31:\/sprint-2024-jan-12-to-2024-jan-31-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We voted for open infra foundation elections<\/li>\n<li>We reviewed persistent host change <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/906433\">https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/906433<\/a><\/li>\n<li>We reviewed custom pod spec for Nodepool: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/nodepool\/+\/907109\">https:\/\/review.opendev.org\/c\/zuul\/nodepool\/+\/907109<\/a><\/li>\n<li>We fixed gitlab \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We voted for open infra foundation elections<\/li>\n<li>We reviewed persistent host change <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/906433\">https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/906433<\/a><\/li>\n<li>We reviewed custom pod spec for Nodepool: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/nodepool\/+\/907109\">https:\/\/review.opendev.org\/c\/zuul\/nodepool\/+\/907109<\/a><\/li>\n<li>We fixed gitlab driver issue (avoid trigger build when reviewers added\/removed) <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/906103\">https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/906103<\/a><\/li>\n<li>We updated the reference gitlab pipeline in doc <a class=\"reference external\" href=\"https:\/\/review.opendev.okjjrg\/c\/zuul\/zuul\/+\/906355\">https:\/\/review.opendev.okjjrg\/c\/zuul\/zuul\/+\/906355<\/a><\/li>\n<li>We help merging gitlab support for blocking comment threads <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/813136\">https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/813136<\/a><\/li>\n<li>We added a feature in logscraper that it can parse many yaml files into one, so there is no need to update the download file, but we can just add new file to be parsed<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul>\n<li><p class=\"first\">We added support for custom gerrit connection secret: RHOSZUUL-1635, and proposed new cr validation logic: <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30697\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30697<\/a><\/p>\n<\/li>\n<li><p class=\"first\">We defined the milestone 6 and closed milestone 5<\/p>\n<\/li>\n<li><p class=\"first\">We released sf-operator 0.0.20 to 0.0.23<\/p>\n<\/li>\n<li><p class=\"first\">We improved the support of the custom CA trust in init-containers <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30558\/\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30558\/<\/a><\/p>\n<\/li>\n<li><p class=\"first\">We documented how to configure a 'config' repo on Gitlab ttps:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30572<\/p>\n<\/li>\n<li><p class=\"first\">We proposed an update on how to mount Zuul source tree  <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30607\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30607<\/a><\/p>\n<\/li>\n<li><p class=\"first\">We explored the feasibility of reducing the size of the Zuul JS bundle<\/p>\n<\/li>\n<li><p class=\"first\">We worked on the CLI overhaul (one binary to rule them all): we reworked and improved most dev-related commands. Some changes are still in review but once merged, we will cut out the legacy CLI.<\/p>\n<\/li>\n<li><p class=\"first\">We moved Zuul images from Stream9 to UBI9<\/p>\n<\/li>\n<li><p class=\"first\">We started to check how to export\/import zuul keys from sf.io to microshift.sf.io<\/p>\n<\/li>\n<li><p class=\"first\">We did a little fixes and improvements:<\/p>\n<p>** Remove getStorageClassname function<\/p>\n<p>** Improved log messages regarding Storage size when it was trying to decrease storage size<\/p>\n<p>** Set LogServerStatus and SoftwareFactoryStatus structures derived from the same base structure<\/p>\n<p>** Fixed some docstring for improve documentation<\/p>\n<p>** Enable GitLab support for the SF's config repo<\/p>\n<p>** Renamed ConfigLocationSpec to ConfigRepositoryLocationSpec<\/p>\n<p>** Fixed trigger variable for a Zuul connection being squashed<\/p>\n<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2023 Dec 22 to 2024 Jan 10 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2023-dec-22-to-2024-jan-10-summary.html","rel":"alternate"}},"published":"2024-01-10T10:00:00+00:00","updated":"2024-01-10T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2024-01-10:\/sprint-2023-dec-22-to-2024-jan-10-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We updated logjuicer role integration: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul-jobs\/+\/899212\">https:\/\/review.opendev.org\/c\/zuul\/zuul-jobs\/+\/899212<\/a><\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We released SF 4.0 0.0.19 alpha and validated deployment<\/li>\n<li>We made a minimal CR installation test in CI <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30439\">https:\/\/softwarefactory-project \u2026<\/a><\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We updated logjuicer role integration: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul-jobs\/+\/899212\">https:\/\/review.opendev.org\/c\/zuul\/zuul-jobs\/+\/899212<\/a><\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We released SF 4.0 0.0.19 alpha and validated deployment<\/li>\n<li>We made a minimal CR installation test in CI <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30439\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30439<\/a><\/li>\n<li>We fixed the logs\/ and nodepool\/builds\/ directory listing since the move to subpathes<\/li>\n<li>We added Secret sf-ssl-cert format detail in sf-operator doc <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30496\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30496<\/a><\/li>\n<li>We improved dev prepare to get a properly configured demo-tenant <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30514\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30514<\/a><\/li>\n<li>We made sure CI job use deploy\/config instead of tmp\/config + use demo-tenant for adding microhsift-pod job <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30517\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30517<\/a><\/li>\n<li>We're working on the CLI overhaul to unify CLIs<\/li>\n<li>We have enabled GitLab support for the SF's config repo<\/li>\n<li>We fixed the trigger variable squashing on the default connection for post pipeline<\/li>\n<li>We ensure Zuul and Nodepool can execute the &quot;update-ca-trust&quot; command<\/li>\n<li>We have Mount a ConfigMap &quot;corporate-ca-certs&quot;, now if the user wants a coporate CA, it can be added.<\/li>\n<li>We have Documented on how to enable corporate CA into Zuul and Nodepool services<\/li>\n<li>We added Zuul Pipeline Bootstrap library for CLI<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2023 Dec 01 to Dec 20 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2023-dec-01-to-dec-20-summary.html","rel":"alternate"}},"published":"2023-12-20T10:00:00+00:00","updated":"2023-12-20T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2023-12-20:\/sprint-2023-dec-01-to-dec-20-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We resurrected <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul-client\/+\/831946\">https:\/\/review.opendev.org\/c\/zuul\/zuul-client\/+\/831946<\/a> to enable SSO auth in zuul-client<\/li>\n<li>We checked the AWS service utilization if we can save money somehow<\/li>\n<li>We changed the hardcoded value for download file list for \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We resurrected <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul-client\/+\/831946\">https:\/\/review.opendev.org\/c\/zuul\/zuul-client\/+\/831946<\/a> to enable SSO auth in zuul-client<\/li>\n<li>We checked the AWS service utilization if we can save money somehow<\/li>\n<li>We changed the hardcoded value for download file list for logscraper and logsender, so the custom path can be configured. That will be helpful to enable pulling logs from Next Gen CI jobs and push it to the OpenSearch RDO.<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We bumped zuul to 9.3.0 and nodepool to 9.1.0<\/li>\n<li>We started working on the CLI overhaul. We are moving commands under one single CLI (operator's main.go) following the Subject &gt; Action &gt; Target pattern.<\/li>\n<li>We worked on adding Elasticsearch and Pagure connection support <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30253\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30253<\/a><\/li>\n<li>We worked on setting Route sub-pathes <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30381\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30381<\/a><\/li>\n<li>We released 0.0.17, 0.0.18 and 0.0.19 versions<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2023 Nov 10 to Nov 29 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2023-nov-10-to-nov-29-summary.html","rel":"alternate"}},"published":"2023-11-29T10:00:00+00:00","updated":"2023-11-29T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2023-11-29:\/sprint-2023-nov-10-to-nov-29-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We fixed the third-party ci sf provides to the zuul-jobs repository<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We improved prometheus annotation of the sf-operator to prevent spurious update<\/li>\n<li>We merged the dev and standalone mode of the sf-operator <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30041\">https:\/\/softwarefactory-project.io\/r \u2026<\/a><\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We fixed the third-party ci sf provides to the zuul-jobs repository<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We improved prometheus annotation of the sf-operator to prevent spurious update<\/li>\n<li>We merged the dev and standalone mode of the sf-operator <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30041\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30041<\/a><\/li>\n<li>We fixed a rendering issue in zuul-weeder where the info page lagged behind the update.<\/li>\n<li>We removed need of an extra and useless PVC for zookeeper. <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30037\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30037<\/a><\/li>\n<li>We added PVC extend feature support for Zookeeper <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30059\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30059<\/a><\/li>\n<li>We set a pod-name selector for all statefulset eg. <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30062\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30062<\/a><\/li>\n<li>We change the owned secret watch mechanism to only relevant secrets to avoid reconcile loop hell <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30087\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30087<\/a><\/li>\n<li>We updated doc to refer to the new standalone mode for developer <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30110\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30110<\/a><\/li>\n<li>We fixed the executor replica not persisted when sf-operator update the Executor sts <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30150\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30150<\/a><\/li>\n<li>We added a scale up test for merger <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30151\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30151<\/a><\/li>\n<li>We added Git and Elasticsearch connections support: such as and followups changes <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30169\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30169<\/a><\/li>\n<li>We added support for secrets populate in CLI for Github and Gitlab support <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30248\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/30248<\/a><\/li>\n<li>We took time to fight against CI flakiness<\/li>\n<li>We tagged and deployed 0.0.13 to 0.0.16 and addressed issues<\/li>\n<li>We fixed runtime issue (and got XP points): zookeeper crash due to Volume full and logserver purglogs delay too long then Volume full too<\/li>\n<li>We added an ADR for backup and restore<\/li>\n<li>Add probes for sshd container in Logserver statefulset<\/li>\n<li>We added log forwarding for most components via a fluent bit sidecar container or a custom python log handler lib<\/li>\n<li>we implemented disk usage monitoring for every statefulset's PV<\/li>\n<li>We moved several containers to use Red Hat's UBI base image (Purgelogs, Sshd, Nodepool Builder and Launcher, Git-Daemon, Zookeper)<\/li>\n<li>We studied a way to use Horizontal Pod scaling Kubernetes feature into sf-operator<\/li>\n<li>We started to move Zuul sf-operator initial configurations to use our golang little library (structs) instead of bash - first step tenant bootstrap<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2023 Oct 20 to Nov 08 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2023-oct-20-to-nov-08-summary.html","rel":"alternate"}},"published":"2023-11-08T10:00:00+00:00","updated":"2023-11-08T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2023-11-08:\/sprint-2023-oct-20-to-nov-08-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We proposed zuul jobs roles to run LogJuicer: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul-jobs\/+\/899212\">https:\/\/review.opendev.org\/c\/zuul\/zuul-jobs\/+\/899212<\/a><\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We Migrated Software Factory project CI Jobs from sf.io to micro.sf.io<\/li>\n<li>We changed some GitHub connection parameters \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We proposed zuul jobs roles to run LogJuicer: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul-jobs\/+\/899212\">https:\/\/review.opendev.org\/c\/zuul\/zuul-jobs\/+\/899212<\/a><\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We Migrated Software Factory project CI Jobs from sf.io to micro.sf.io<\/li>\n<li>We changed some GitHub connection parameters to kubernetes secrets ( previously they were set in the SF CR )<\/li>\n<li>We have added GitLab connection support to Zuul Connections in sf-operator<\/li>\n<li>We set Golang Environment Variable due to an update in golang 1.21<\/li>\n<li>We are working on a MariaDB Container based on UBI 9 Image<\/li>\n<li>We investigated Kubernetes Horizontal Pod Scaling to implement it sf-operator project:\n* Added Horizontal Pod Autoscaler to Zuul Executor\n* Add metrics-server to CI jobs\n* Add metrics-server stress test<\/li>\n<li>We added the zuul pub key to nodepool-builder <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29786\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29786<\/a><\/li>\n<li>We resized nodepool-builder \/var\/lib\/nodepool <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29785\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29785<\/a><\/li>\n<li>We wrote nodepool-builder ADR <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29797\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29797<\/a><\/li>\n<li>We prevented .ssh\/known_hosts to be wiped <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29812\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29812<\/a><\/li>\n<li>We added a standalone mode for sf-operator <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/q\/topic:standalone+repo:software-factory\/sf-operator\">https:\/\/softwarefactory-project.io\/r\/q\/topic:standalone+repo:software-factory\/sf-operator<\/a> without cluster admin<\/li>\n<li>we started work on providing an alternative for log collection with zuul &amp; nodepool, based on Fluent Bit. (Demo maybe)<\/li>\n<li>We're publishing our docs automatically on github.io, API documentation is autogenerated as well (Demo for sure)<\/li>\n<li>We are working on merging the dev and standalone mode:\n* dev = operator straight from the source, just &quot;go run&quot;\n* standalone is not using CR, just plain config, w\/o cluster admin<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Leveraging Cap'n Proto For Logreduce Reports","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/leveraging-capn-proto-for-logreduce-reports.html","rel":"alternate"}},"published":"2023-10-23T00:00:00+00:00","updated":"2023-10-23T00:00:00+00:00","author":{"name":"tristanC"},"id":"tag:www.softwarefactory-project.io,2023-10-23:\/leveraging-capn-proto-for-logreduce-reports.html","summary":"<style type=\"text\/css\">\n  blockquote {\n    font-size: small;\n    padding: 0px 5px;\n  }\n<\/style><!-- This work is licensed under the Creative Commons Attribution 4.0 International License.\n     To view a copy of this license, visit http:\/\/creativecommons.org\/licenses\/by\/4.0\/\n     or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.\n--><!--  -->\n<blockquote>\nThis post is a follow-up on the previous <a class=\"reference external\" href=\"https:\/\/www.softwarefactory-project.io\/logreduce-wasm-based-web-interface.html\">WASM based web interface<\/a>\narticle.<\/blockquote>\n<p>This post describes why and how I used <a class=\"reference external\" href=\"https:\/\/capnproto.org\/\">Cap\u2019n Proto<\/a> for the\n<a class=\"reference external\" href=\"https:\/\/github.com\/logreduce\/logreduce#readme\">logreduce<\/a> reports format. In three parts, I present:<\/p>\n<ul class=\"simple\">\n<li>Bincode versioning scheme.<\/li>\n<li>Cap\u2019n Proto.<\/li>\n<li>Logreduce report encoder\/decoder \u2026<\/li><\/ul>","content":"<style type=\"text\/css\">\n  blockquote {\n    font-size: small;\n    padding: 0px 5px;\n  }\n<\/style><!-- This work is licensed under the Creative Commons Attribution 4.0 International License.\n     To view a copy of this license, visit http:\/\/creativecommons.org\/licenses\/by\/4.0\/\n     or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.\n--><!--  -->\n<blockquote>\nThis post is a follow-up on the previous <a class=\"reference external\" href=\"https:\/\/www.softwarefactory-project.io\/logreduce-wasm-based-web-interface.html\">WASM based web interface<\/a>\narticle.<\/blockquote>\n<p>This post describes why and how I used <a class=\"reference external\" href=\"https:\/\/capnproto.org\/\">Cap\u2019n Proto<\/a> for the\n<a class=\"reference external\" href=\"https:\/\/github.com\/logreduce\/logreduce#readme\">logreduce<\/a> reports format. In three parts, I present:<\/p>\n<ul class=\"simple\">\n<li>Bincode versioning scheme.<\/li>\n<li>Cap\u2019n Proto.<\/li>\n<li>Logreduce report encoder\/decoder.<\/li>\n<\/ul>\n<div class=\"section\" id=\"context-and-problem-statement\">\n<h2>Context and problem statement<\/h2>\n<p>Logreduce is a tool that searches for anomalies in build logs. It can\nproduce reports displayable on web browsers. Logreduce used to\ndistribute an HTML file setup with a compatible rendering client.\nHowever, in the context of the new web service interface, the client may\nnow display reports that were created by an older version of logreduce.<\/p>\n<p>The problem is that the report format didn't guarantee backward\ncompatibility: clients were not able to read reports saved in a previous\nversion.<\/p>\n<p>I evaluated the following formats to solve this problem:<\/p>\n<ul class=\"simple\">\n<li><a class=\"reference external\" href=\"https:\/\/protobuf.dev\/\">Protobuf<\/a>, introduced by Google in 2001.<\/li>\n<li><a class=\"reference external\" href=\"https:\/\/thrift.apache.org\/\">Thrift<\/a>, introduced by Facebook in 2007.<\/li>\n<li><a class=\"reference external\" href=\"https:\/\/capnproto.org\/\">Cap\u2019n Proto<\/a>, introduced by the former maintainer of Protobuf in\n2013.<\/li>\n<li><a class=\"reference external\" href=\"https:\/\/flatbuffers.dev\/\">Flatbuffers<\/a>, introduced by Google in 2014.<\/li>\n<\/ul>\n<p>Cap'n Proto and Flatbuffers are modern formats designed for\nperformance-critical applications. They both enable access to the data\nwithout parsing\/unpacking, using a process known as zero-copy\nserialization, which is a great feature for logreduce. Flatbuffers was\noriginally created for games development and it doesn't perform data\nvalidation by default. Therefore I decided to use Cap'n Proto as\ndiscussed in the next sections.<\/p>\n<\/div>\n<div class=\"section\" id=\"bincode-versioning-scheme\">\n<h2>Bincode versioning scheme<\/h2>\n<p>In this section I present the main challenge of using bincode to save\ndata. Previously, logreduce used bincode to exchange reports.<\/p>\n<div class=\"section\" id=\"prepare-the-playground\">\n<h3>Prepare the playground<\/h3>\n<p>For the purpose of this article, we'll create a standalone playground.<\/p>\n<blockquote>\nIf you don't have <tt class=\"docutils literal\">cargo<\/tt>, see this <a class=\"reference external\" href=\"https:\/\/www.rust-lang.org\/tools\/install\">install rust<\/a> documentation.<\/blockquote>\n<p>Setup a new project:<\/p>\n<div class=\"highlight\"><pre><span><\/span>$ cargo new capnp-playground\n$ cd capnp-playground\n<\/pre><\/div>\n<p>Add dependencies:<\/p>\n<div class=\"highlight\"><pre><span><\/span>$ cargo add bincode@1.3\n<\/pre><\/div>\n<p>Add serde with the derive feature to generate the encoder\/decoder:<\/p>\n<div class=\"highlight\"><pre><span><\/span>$ cargo add serde@1.0 --features derive\n<\/pre><\/div>\n<p>And build everything:<\/p>\n<div class=\"highlight\"><pre><span><\/span>$ cargo run\nHello, world!\n<\/pre><\/div>\n<\/div>\n<div class=\"section\" id=\"create-the-initial-report\">\n<h3>Create the initial report<\/h3>\n<p>Add the following code to demonstrate bincode usage in the\n<tt class=\"docutils literal\">src\/main.rs<\/tt> file:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"c1\">\/\/ Copyright (C) 2023 Red Hat<\/span>\n<span class=\"c1\">\/\/ SPDX-License-Identifier: Apache-2.0<\/span>\n\n<span class=\"c1\">\/\/ This program demonstrates data type serialization.<\/span>\n<span class=\"c1\">\/\/ It does not handle exceptions and unwrap is used to keep the code short.<\/span>\n\n<span class=\"k\">use<\/span><span class=\"w\"> <\/span><span class=\"n\">serde<\/span><span class=\"p\">::{<\/span><span class=\"n\">Deserialize<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">Serialize<\/span><span class=\"p\">};<\/span>\n<span class=\"k\">use<\/span><span class=\"w\"> <\/span><span class=\"n\">std<\/span><span class=\"p\">::<\/span><span class=\"n\">fs<\/span><span class=\"p\">::<\/span><span class=\"n\">File<\/span><span class=\"p\">;<\/span>\n\n<span class=\"cp\">#[derive(Debug, Serialize, Deserialize)]<\/span>\n<span class=\"k\">struct<\/span><span class=\"w\"> <\/span><span class=\"nc\">Report<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">baselines<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nb\">Vec<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">Content<\/span><span class=\"o\">&gt;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ list of anomaly omitted<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"cp\">#[derive(Debug, Serialize, Deserialize)]<\/span>\n<span class=\"k\">enum<\/span><span class=\"w\"> <\/span><span class=\"nc\">Content<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">Zuul<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">change<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"kt\">u64<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">job<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nb\">String<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">Prow<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">pr<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"kt\">u64<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">url<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nb\">String<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">},<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"k\">fn<\/span><span class=\"w\"> <\/span><span class=\"nf\">encode<\/span><span class=\"p\">(<\/span><span class=\"n\">report<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"kp\">&amp;<\/span><span class=\"nc\">Report<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">file<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"kp\">&amp;<\/span><span class=\"kt\">str<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"fm\">println!<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;{}: saving report&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">file<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">file<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">File<\/span><span class=\"p\">::<\/span><span class=\"n\">create<\/span><span class=\"p\">(<\/span><span class=\"n\">file<\/span><span class=\"p\">).<\/span><span class=\"n\">unwrap<\/span><span class=\"p\">();<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">bincode<\/span><span class=\"p\">::<\/span><span class=\"n\">serialize_into<\/span><span class=\"p\">(<\/span><span class=\"n\">file<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">report<\/span><span class=\"p\">).<\/span><span class=\"n\">unwrap<\/span><span class=\"p\">();<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"k\">fn<\/span><span class=\"w\"> <\/span><span class=\"nf\">decode<\/span><span class=\"p\">(<\/span><span class=\"n\">file<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"kp\">&amp;<\/span><span class=\"kt\">str<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"nc\">Report<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"fm\">println!<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;{}: loading report&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">file<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">file<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">File<\/span><span class=\"p\">::<\/span><span class=\"n\">open<\/span><span class=\"p\">(<\/span><span class=\"n\">file<\/span><span class=\"p\">).<\/span><span class=\"n\">unwrap<\/span><span class=\"p\">();<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">bincode<\/span><span class=\"p\">::<\/span><span class=\"n\">deserialize_from<\/span><span class=\"p\">(<\/span><span class=\"n\">file<\/span><span class=\"p\">).<\/span><span class=\"n\">unwrap<\/span><span class=\"p\">()<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"k\">fn<\/span><span class=\"w\"> <\/span><span class=\"nf\">main<\/span><span class=\"p\">()<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">match<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"n\">std<\/span><span class=\"p\">::<\/span><span class=\"n\">env<\/span><span class=\"p\">::<\/span><span class=\"n\">args<\/span><span class=\"p\">().<\/span><span class=\"n\">collect<\/span><span class=\"p\">::<\/span><span class=\"o\">&lt;<\/span><span class=\"nb\">Vec<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">_<\/span><span class=\"o\">&gt;&gt;<\/span><span class=\"p\">()[<\/span><span class=\"o\">..<\/span><span class=\"p\">]<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">[<\/span><span class=\"n\">_<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">cmd<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">fp<\/span><span class=\"p\">]<\/span><span class=\"w\"> <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"n\">cmd<\/span><span class=\"w\"> <\/span><span class=\"o\">==<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;encode&quot;<\/span><span class=\"w\"> <\/span><span class=\"o\">=&gt;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">            <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">report<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">Report<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                <\/span><span class=\"n\">baselines<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nc\">vec<\/span><span class=\"o\">!<\/span><span class=\"p\">[<\/span><span class=\"n\">Content<\/span><span class=\"p\">::<\/span><span class=\"n\">Zuul<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                    <\/span><span class=\"n\">change<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"mi\">42<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">                    <\/span><span class=\"n\">job<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;test&quot;<\/span><span class=\"p\">.<\/span><span class=\"n\">to_string<\/span><span class=\"p\">(),<\/span>\n<span class=\"w\">                <\/span><span class=\"p\">}],<\/span>\n<span class=\"w\">            <\/span><span class=\"p\">};<\/span>\n<span class=\"w\">            <\/span><span class=\"n\">encode<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"n\">report<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">fp<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">[<\/span><span class=\"n\">_<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">cmd<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">fp<\/span><span class=\"p\">]<\/span><span class=\"w\"> <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"n\">cmd<\/span><span class=\"w\"> <\/span><span class=\"o\">==<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;decode&quot;<\/span><span class=\"w\"> <\/span><span class=\"o\">=&gt;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">            <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">report<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">decode<\/span><span class=\"p\">(<\/span><span class=\"n\">fp<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">            <\/span><span class=\"fm\">println!<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;got: {:?}&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">report<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">_<\/span><span class=\"w\"> <\/span><span class=\"o\">=&gt;<\/span><span class=\"w\"> <\/span><span class=\"fm\">eprintln!<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;usage: encode|decode file&quot;<\/span><span class=\"p\">),<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">};<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<p>Run the following commands to perform a serialization round trip:<\/p>\n<div class=\"highlight\"><pre><span><\/span>$ cargo run -- encode report.bin\nreport.bin: saving report\n\n$ cargo run -- decode report.bin\nreport.bin: loading report\ngot: Report { baselines: [Zuul { change: 42, job: &quot;test&quot; }] }\n<\/pre><\/div>\n<\/div>\n<div class=\"section\" id=\"updating-the-schema\">\n<h3>Updating the schema<\/h3>\n<p>Update the schema, for example, by adding a new field to the Zuul\nstructure:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"gd\">--- a\/src\/main.rs<\/span>\n<span class=\"gi\">+++ b\/src\/main.rs<\/span>\n<span class=\"gu\">@@ -14,6 +14,7 @@ enum Content {<\/span>\n<span class=\"w\"> <\/span>    Zuul {\n<span class=\"w\"> <\/span>        change: u64,\n<span class=\"w\"> <\/span>        job: String,\n<span class=\"gi\">+        project: String,<\/span>\n<span class=\"w\"> <\/span>    },\n<span class=\"w\"> <\/span>    Prow {\n<span class=\"w\"> <\/span>        pr: u64,\n\n<span class=\"gu\">@@ -38,6 +38,7 @@ fn main() {<\/span>\n<span class=\"w\"> <\/span>                baselines: vec![Content::Zuul {\n<span class=\"w\"> <\/span>                    change: 42,\n<span class=\"w\"> <\/span>                    job: &quot;test&quot;.to_string(),\n<span class=\"gi\">+                    project: &quot;demo&quot;.to_string(),<\/span>\n<span class=\"w\"> <\/span>                }],\n<span class=\"w\"> <\/span>            };\n<span class=\"w\"> <\/span>            encode(&amp;report, fp);\n<\/pre><\/div>\n<p>Now, decoding the initial report produces this error:<\/p>\n<div class=\"highlight\"><pre><span><\/span>$ cargo run -- decode report.bin\nreport.bin: loading report\nthread &#39;main&#39; panicked at src\/main.rs:42:37:\ncalled `Result::unwrap()` on an `Err` value: Io(Error {\n  kind: UnexpectedEof,\n  message: &quot;failed to fill whole buffer&quot;\n})\n<\/pre><\/div>\n<p>That is expected: bincode is not able to deserialize the previous report\nbecause it now expects that Zuul builds have a project. To address that,\nwe need to use a versioning scheme, for example with such a data type:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"k\">enum<\/span><span class=\"w\"> <\/span><span class=\"nc\">Report<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">  <\/span><span class=\"n\">V1<\/span><span class=\"p\">(<\/span><span class=\"n\">ReportV1<\/span><span class=\"p\">),<\/span>\n<span class=\"w\">  <\/span><span class=\"n\">V2<\/span><span class=\"p\">(<\/span><span class=\"n\">ReportV2<\/span><span class=\"p\">)<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<p>As long as we only append new variants, bincode is able to decode\nreports saved in a previous version. However this is not very practical\nbecause any change will introduce a new top level version.<\/p>\n<p>Moreover, bincode doesn't check the enum tag. If we move the <tt class=\"docutils literal\">Prow<\/tt>\nvariant at the top of the <tt class=\"docutils literal\">Content<\/tt> declaration, then bincode will\nhappily load the report using the wrong tag because the existing data\nfits the shape.<\/p>\n<p>In the next section, I introduce a different format to handle versioning\nefficiently.<\/p>\n<\/div>\n<\/div>\n<div class=\"section\" id=\"introducing-capn-proto\">\n<h2>Introducing Cap\u2019n Proto<\/h2>\n<p>Cap\u2019n Proto is a fast data interchange format. The main benefits are:<\/p>\n<ul class=\"simple\">\n<li>strongly-typed schema with first class support for <a class=\"reference external\" href=\"https:\/\/en.wikipedia.org\/wiki\/Algebraic_data_type\">algebraic data\ntypes<\/a> and generic types.<\/li>\n<li>backward compatible message.<\/li>\n<li>zero-copy serialization.<\/li>\n<\/ul>\n<div class=\"section\" id=\"schema-language\">\n<h3>Schema Language<\/h3>\n<p>The data format is defined using a special language. Here is the schema\nfor the report used in the playground above, copy this to a file named\n<tt class=\"docutils literal\">schema.capnp<\/tt> at the root of the project:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"nd\">@0xa0b4401e03756e61<\/span>;\n\n<span class=\"k\">struct<\/span><span class=\"w\"> <\/span><span class=\"n\">Report<\/span><span class=\"w\"> <\/span>{\n<span class=\"w\">  <\/span><span class=\"n\">baselines<\/span><span class=\"w\"> <\/span><span class=\"nd\">@0<\/span><span class=\"w\"> <\/span><span class=\"nc\">:List(Content)<\/span>;\n}\n\n<span class=\"k\">struct<\/span><span class=\"w\"> <\/span><span class=\"n\">Content<\/span><span class=\"w\"> <\/span>{\n<span class=\"w\">  <\/span><span class=\"k\">union<\/span><span class=\"w\"> <\/span>{\n<span class=\"w\">    <\/span><span class=\"n\">zuul<\/span><span class=\"w\">    <\/span><span class=\"nd\">@0<\/span><span class=\"w\"> <\/span><span class=\"nc\">:Zuul<\/span>;\n<span class=\"w\">    <\/span><span class=\"n\">prow<\/span><span class=\"w\">    <\/span><span class=\"nd\">@1<\/span><span class=\"w\"> <\/span><span class=\"nc\">:Prow<\/span>;\n<span class=\"w\">  <\/span>}\n\n<span class=\"w\">  <\/span><span class=\"k\">struct<\/span><span class=\"w\"> <\/span><span class=\"n\">Zuul<\/span><span class=\"w\"> <\/span>{\n<span class=\"w\">    <\/span><span class=\"n\">change<\/span><span class=\"w\">  <\/span><span class=\"nd\">@0<\/span><span class=\"w\"> <\/span><span class=\"nc\">:UInt64<\/span>;\n<span class=\"w\">    <\/span><span class=\"n\">job<\/span><span class=\"w\">     <\/span><span class=\"nd\">@1<\/span><span class=\"w\"> <\/span><span class=\"nc\">:Text<\/span>;\n<span class=\"w\">    <\/span><span class=\"n\">project<\/span><span class=\"w\"> <\/span><span class=\"nd\">@2<\/span><span class=\"w\"> <\/span><span class=\"nc\">:Text<\/span>;\n<span class=\"w\">  <\/span>}\n\n<span class=\"w\">  <\/span><span class=\"k\">struct<\/span><span class=\"w\"> <\/span><span class=\"n\">Prow<\/span><span class=\"w\"> <\/span>{\n<span class=\"w\">    <\/span><span class=\"n\">pr<\/span><span class=\"w\">      <\/span><span class=\"nd\">@0<\/span><span class=\"w\"> <\/span><span class=\"nc\">:UInt64<\/span>;\n<span class=\"w\">    <\/span><span class=\"n\">url<\/span><span class=\"w\">     <\/span><span class=\"nd\">@1<\/span><span class=\"w\"> <\/span><span class=\"nc\">:Text<\/span>;\n<span class=\"w\">  <\/span>}\n}\n<\/pre><\/div>\n<p>This should be self explanatory. Checkout the full logreduce report\nschema in this <a class=\"reference external\" href=\"https:\/\/github.com\/logreduce\/logreduce\/blob\/main\/crates\/report\/schema.capnp\">report\/schema.capnp<\/a>, and the <a class=\"reference external\" href=\"https:\/\/capnproto.org\/language.html\">language documentation<\/a>\nto learn more about it.<\/p>\n<\/div>\n<div class=\"section\" id=\"code-generation\">\n<h3>Code generation<\/h3>\n<p>Cap'n Proto provides a compiler named <tt class=\"docutils literal\">capnpc<\/tt> to generate code for\n<a class=\"reference external\" href=\"https:\/\/capnproto.org\/otherlang.html\">various languages<\/a>. Copy the following build instructions to a file\nnamed <tt class=\"docutils literal\">build.rs<\/tt> at the root of the project:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"k\">fn<\/span><span class=\"w\"> <\/span><span class=\"nf\">main<\/span><span class=\"p\">()<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">capnpc<\/span><span class=\"p\">::<\/span><span class=\"n\">CompilerCommand<\/span><span class=\"p\">::<\/span><span class=\"n\">new<\/span><span class=\"p\">()<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">.<\/span><span class=\"n\">file<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;schema.capnp&quot;<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">.<\/span><span class=\"n\">output_path<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;generated\/&quot;<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">.<\/span><span class=\"n\">run<\/span><span class=\"p\">()<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">.<\/span><span class=\"n\">expect<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;compiling schema.capnp&quot;<\/span><span class=\"p\">);<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<p>Get the compiler by installing <tt class=\"docutils literal\">capnproto<\/tt> using your favorite package\nmanager, then run the following commands to generate the code:<\/p>\n<div class=\"highlight\"><pre><span><\/span>$ cargo add --build capnpc@0.18 &amp;&amp; cargo add capnp@0.18\n$ cargo build\n<\/pre><\/div>\n<p>Integrate the generated code in the <tt class=\"docutils literal\">main.rs<\/tt> file by adding:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"k\">mod<\/span><span class=\"w\"> <\/span><span class=\"nn\">schema_capnp<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"cp\">#![allow(dead_code, unused_qualifications)]<\/span>\n<span class=\"w\">    <\/span><span class=\"fm\">include!<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;..\/generated\/schema_capnp.rs&quot;<\/span><span class=\"p\">);<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<p>This setup introduces new Reader and Builder data types to read and\nwrite reports according to the schema definition.<\/p>\n<p>In the next section I show how to use the new data types.<\/p>\n<\/div>\n<\/div>\n<div class=\"section\" id=\"report-encoder-decoder\">\n<h2>Report Encoder\/Decoder<\/h2>\n<p>As an example usage of the generated data types, we can implement an\nencoder\/decoder for the existing report struct.<\/p>\n<div class=\"section\" id=\"encode-a-report\">\n<h3>Encode a report<\/h3>\n<p>Here is how to write a report using the <tt class=\"docutils literal\"><span class=\"pre\">capnp::message<\/span><\/tt> module:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"c1\">\/\/ This function write the report to the argument implementing the Write trait.<\/span>\n<span class=\"k\">fn<\/span><span class=\"w\"> <\/span><span class=\"nf\">capnp_encode<\/span><span class=\"p\">(<\/span><span class=\"n\">report<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"kp\">&amp;<\/span><span class=\"nc\">Report<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">write<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nc\">impl<\/span><span class=\"w\"> <\/span><span class=\"n\">capnp<\/span><span class=\"p\">::<\/span><span class=\"n\">io<\/span><span class=\"p\">::<\/span><span class=\"n\">Write<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ Prepare a report message builder<\/span>\n<span class=\"w\">    <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"k\">mut<\/span><span class=\"w\"> <\/span><span class=\"n\">message<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">capnp<\/span><span class=\"p\">::<\/span><span class=\"n\">message<\/span><span class=\"p\">::<\/span><span class=\"n\">Builder<\/span><span class=\"p\">::<\/span><span class=\"n\">new_default<\/span><span class=\"p\">();<\/span>\n<span class=\"w\">    <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"k\">mut<\/span><span class=\"w\"> <\/span><span class=\"n\">report_builder<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">message<\/span><span class=\"p\">.<\/span><span class=\"n\">init_root<\/span><span class=\"p\">::<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">schema_capnp<\/span><span class=\"p\">::<\/span><span class=\"n\">report<\/span><span class=\"p\">::<\/span><span class=\"n\">Builder<\/span><span class=\"o\">&gt;<\/span><span class=\"p\">();<\/span>\n\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ Write a single content.<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">fn<\/span><span class=\"w\"> <\/span><span class=\"nf\">write_content<\/span><span class=\"p\">(<\/span><span class=\"n\">content<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"kp\">&amp;<\/span><span class=\"nc\">Content<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">builder<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nc\">schema_capnp<\/span><span class=\"p\">::<\/span><span class=\"n\">content<\/span><span class=\"p\">::<\/span><span class=\"n\">Builder<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"k\">match<\/span><span class=\"w\"> <\/span><span class=\"n\">content<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">            <\/span><span class=\"n\">Content<\/span><span class=\"p\">::<\/span><span class=\"n\">Zuul<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                <\/span><span class=\"n\">change<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">                <\/span><span class=\"n\">job<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">                <\/span><span class=\"n\">project<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">            <\/span><span class=\"p\">}<\/span><span class=\"w\"> <\/span><span class=\"o\">=&gt;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                <\/span><span class=\"c1\">\/\/ Prepare a zuul builder.<\/span>\n<span class=\"w\">                <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"k\">mut<\/span><span class=\"w\"> <\/span><span class=\"n\">builder<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">builder<\/span><span class=\"p\">.<\/span><span class=\"n\">init_zuul<\/span><span class=\"p\">();<\/span>\n<span class=\"w\">                <\/span><span class=\"c1\">\/\/ Write the fields<\/span>\n<span class=\"w\">                <\/span><span class=\"n\">builder<\/span><span class=\"p\">.<\/span><span class=\"n\">set_change<\/span><span class=\"p\">(<\/span><span class=\"o\">*<\/span><span class=\"n\">change<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">                <\/span><span class=\"n\">builder<\/span><span class=\"p\">.<\/span><span class=\"n\">set_job<\/span><span class=\"p\">(<\/span><span class=\"n\">job<\/span><span class=\"p\">.<\/span><span class=\"n\">as_str<\/span><span class=\"p\">().<\/span><span class=\"n\">into<\/span><span class=\"p\">());<\/span>\n<span class=\"w\">                <\/span><span class=\"n\">builder<\/span><span class=\"p\">.<\/span><span class=\"n\">set_project<\/span><span class=\"p\">(<\/span><span class=\"n\">project<\/span><span class=\"p\">.<\/span><span class=\"n\">as_str<\/span><span class=\"p\">().<\/span><span class=\"n\">into<\/span><span class=\"p\">());<\/span>\n<span class=\"w\">            <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">            <\/span><span class=\"n\">Content<\/span><span class=\"p\">::<\/span><span class=\"n\">Prow<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"> <\/span><span class=\"n\">pr<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">url<\/span><span class=\"w\"> <\/span><span class=\"p\">}<\/span><span class=\"w\"> <\/span><span class=\"o\">=&gt;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                <\/span><span class=\"c1\">\/\/ Prepare a prow builder.<\/span>\n<span class=\"w\">                <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"k\">mut<\/span><span class=\"w\"> <\/span><span class=\"n\">builder<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">builder<\/span><span class=\"p\">.<\/span><span class=\"n\">init_prow<\/span><span class=\"p\">();<\/span>\n<span class=\"w\">                <\/span><span class=\"c1\">\/\/ Write the fields<\/span>\n<span class=\"w\">                <\/span><span class=\"n\">builder<\/span><span class=\"p\">.<\/span><span class=\"n\">set_pr<\/span><span class=\"p\">(<\/span><span class=\"o\">*<\/span><span class=\"n\">pr<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">                <\/span><span class=\"n\">builder<\/span><span class=\"p\">.<\/span><span class=\"n\">set_url<\/span><span class=\"p\">(<\/span><span class=\"n\">url<\/span><span class=\"p\">.<\/span><span class=\"n\">as_str<\/span><span class=\"p\">().<\/span><span class=\"n\">into<\/span><span class=\"p\">());<\/span>\n<span class=\"w\">            <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ Write the baselines vector<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"c1\">\/\/ Prepare the list builder.<\/span>\n<span class=\"w\">        <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"k\">mut<\/span><span class=\"w\"> <\/span><span class=\"n\">baselines_builder<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">report_builder<\/span>\n<span class=\"w\">            <\/span><span class=\"p\">.<\/span><span class=\"n\">reborrow<\/span><span class=\"p\">()<\/span>\n<span class=\"w\">            <\/span><span class=\"p\">.<\/span><span class=\"n\">init_baselines<\/span><span class=\"p\">(<\/span><span class=\"n\">report<\/span><span class=\"p\">.<\/span><span class=\"n\">baselines<\/span><span class=\"p\">.<\/span><span class=\"n\">len<\/span><span class=\"p\">()<\/span><span class=\"w\"> <\/span><span class=\"k\">as<\/span><span class=\"w\"> <\/span><span class=\"kt\">u32<\/span><span class=\"p\">);<\/span>\n\n<span class=\"w\">        <\/span><span class=\"k\">for<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">idx<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">content<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"k\">in<\/span><span class=\"w\"> <\/span><span class=\"n\">report<\/span><span class=\"p\">.<\/span><span class=\"n\">baselines<\/span><span class=\"p\">.<\/span><span class=\"n\">iter<\/span><span class=\"p\">().<\/span><span class=\"n\">enumerate<\/span><span class=\"p\">()<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">            <\/span><span class=\"c1\">\/\/ Prepare the list element builder.<\/span>\n<span class=\"w\">            <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">content_builder<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">baselines_builder<\/span><span class=\"p\">.<\/span><span class=\"n\">reborrow<\/span><span class=\"p\">().<\/span><span class=\"n\">get<\/span><span class=\"p\">(<\/span><span class=\"n\">idx<\/span><span class=\"w\"> <\/span><span class=\"k\">as<\/span><span class=\"w\"> <\/span><span class=\"kt\">u32<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">            <\/span><span class=\"c1\">\/\/ Write the individual baseline.<\/span>\n<span class=\"w\">            <\/span><span class=\"n\">write_content<\/span><span class=\"p\">(<\/span><span class=\"n\">content<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">content_builder<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ Write the message<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">capnp<\/span><span class=\"p\">::<\/span><span class=\"n\">serialize<\/span><span class=\"p\">::<\/span><span class=\"n\">write_message<\/span><span class=\"p\">(<\/span><span class=\"n\">write<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"n\">message<\/span><span class=\"p\">).<\/span><span class=\"n\">unwrap<\/span><span class=\"p\">();<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<p>Update the encode helper:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"gu\">@@ -29,7 +84,7 @@ enum Content {<\/span>\n<span class=\"w\"> <\/span>fn encode(report: &amp;Report, file: &amp;str) {\n<span class=\"w\"> <\/span>    println!(&quot;{}: saving report&quot;, file);\n<span class=\"w\"> <\/span>    let file = File::create(file).unwrap();\n<span class=\"gd\">-    bincode::serialize_into(file, report).unwrap();<\/span>\n<span class=\"gi\">+    capnp_encode(report, file)<\/span>\n<span class=\"w\"> <\/span>}\n<\/pre><\/div>\n<p>Run the following command to demonstrate the encoding:<\/p>\n<div class=\"highlight\"><pre><span><\/span>$ cargo run -- encode report.msg\nreport.msg: saving report\n<\/pre><\/div>\n<\/div>\n<div class=\"section\" id=\"decode-a-report\">\n<h3>Decode a report<\/h3>\n<p>Here is how to read a report:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"c1\">\/\/ This function read the report from the argument implementing the BufRead trait.<\/span>\n<span class=\"k\">fn<\/span><span class=\"w\"> <\/span><span class=\"nf\">capnp_decode<\/span><span class=\"p\">(<\/span><span class=\"n\">bufread<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nc\">impl<\/span><span class=\"w\"> <\/span><span class=\"n\">capnp<\/span><span class=\"p\">::<\/span><span class=\"n\">io<\/span><span class=\"p\">::<\/span><span class=\"n\">BufRead<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"nc\">Report<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">message_reader<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">capnp<\/span><span class=\"p\">::<\/span><span class=\"n\">serialize<\/span><span class=\"p\">::<\/span><span class=\"n\">read_message<\/span><span class=\"p\">(<\/span><span class=\"n\">bufread<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">capnp<\/span><span class=\"p\">::<\/span><span class=\"n\">message<\/span><span class=\"p\">::<\/span><span class=\"n\">ReaderOptions<\/span><span class=\"p\">::<\/span><span class=\"n\">new<\/span><span class=\"p\">()).<\/span><span class=\"n\">unwrap<\/span><span class=\"p\">();<\/span>\n\n<span class=\"w\">    <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">report_reader<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">message_reader<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">.<\/span><span class=\"n\">get_root<\/span><span class=\"p\">::<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">schema_capnp<\/span><span class=\"p\">::<\/span><span class=\"n\">report<\/span><span class=\"p\">::<\/span><span class=\"n\">Reader<\/span><span class=\"o\">&lt;&#39;<\/span><span class=\"nb\">_<\/span><span class=\"o\">&gt;&gt;<\/span><span class=\"p\">()<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">.<\/span><span class=\"n\">unwrap<\/span><span class=\"p\">();<\/span>\n\n<span class=\"w\">    <\/span><span class=\"k\">fn<\/span><span class=\"w\"> <\/span><span class=\"nf\">read_content<\/span><span class=\"p\">(<\/span><span class=\"n\">reader<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"kp\">&amp;<\/span><span class=\"nc\">schema_capnp<\/span><span class=\"p\">::<\/span><span class=\"n\">content<\/span><span class=\"p\">::<\/span><span class=\"n\">Reader<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"nc\">Content<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"k\">use<\/span><span class=\"w\"> <\/span><span class=\"n\">schema_capnp<\/span><span class=\"p\">::<\/span><span class=\"n\">content<\/span><span class=\"p\">::<\/span><span class=\"n\">Which<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">        <\/span><span class=\"c1\">\/\/ Read the generated union data type<\/span>\n<span class=\"w\">        <\/span><span class=\"k\">match<\/span><span class=\"w\"> <\/span><span class=\"n\">reader<\/span><span class=\"p\">.<\/span><span class=\"n\">which<\/span><span class=\"p\">().<\/span><span class=\"n\">unwrap<\/span><span class=\"p\">()<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">            <\/span><span class=\"n\">Which<\/span><span class=\"p\">::<\/span><span class=\"n\">Zuul<\/span><span class=\"p\">(<\/span><span class=\"n\">reader<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"o\">=&gt;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                <\/span><span class=\"c1\">\/\/ Prepare the reader<\/span>\n<span class=\"w\">                <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">reader<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">reader<\/span><span class=\"p\">.<\/span><span class=\"n\">unwrap<\/span><span class=\"p\">();<\/span>\n<span class=\"w\">                <\/span><span class=\"c1\">\/\/ Read the fields<\/span>\n<span class=\"w\">                <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">change<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">reader<\/span><span class=\"p\">.<\/span><span class=\"n\">get_change<\/span><span class=\"p\">();<\/span>\n<span class=\"w\">                <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">job<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">reader<\/span><span class=\"p\">.<\/span><span class=\"n\">get_job<\/span><span class=\"p\">().<\/span><span class=\"n\">unwrap<\/span><span class=\"p\">().<\/span><span class=\"n\">to_str<\/span><span class=\"p\">().<\/span><span class=\"n\">unwrap<\/span><span class=\"p\">().<\/span><span class=\"n\">into<\/span><span class=\"p\">();<\/span>\n<span class=\"w\">                <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">project<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">reader<\/span><span class=\"p\">.<\/span><span class=\"n\">get_project<\/span><span class=\"p\">().<\/span><span class=\"n\">unwrap<\/span><span class=\"p\">().<\/span><span class=\"n\">to_str<\/span><span class=\"p\">().<\/span><span class=\"n\">unwrap<\/span><span class=\"p\">().<\/span><span class=\"n\">into<\/span><span class=\"p\">();<\/span>\n<span class=\"w\">                <\/span><span class=\"n\">Content<\/span><span class=\"p\">::<\/span><span class=\"n\">Zuul<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                    <\/span><span class=\"n\">change<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">                    <\/span><span class=\"n\">job<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">                    <\/span><span class=\"n\">project<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">                <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">            <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">            <\/span><span class=\"n\">Which<\/span><span class=\"p\">::<\/span><span class=\"n\">Prow<\/span><span class=\"p\">(<\/span><span class=\"n\">reader<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"o\">=&gt;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                <\/span><span class=\"c1\">\/\/ Prepare the reader<\/span>\n<span class=\"w\">                <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">reader<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">reader<\/span><span class=\"p\">.<\/span><span class=\"n\">unwrap<\/span><span class=\"p\">();<\/span>\n<span class=\"w\">                <\/span><span class=\"c1\">\/\/ Read the fields<\/span>\n<span class=\"w\">                <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">pr<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">reader<\/span><span class=\"p\">.<\/span><span class=\"n\">get_pr<\/span><span class=\"p\">();<\/span>\n<span class=\"w\">                <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">url<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">reader<\/span><span class=\"p\">.<\/span><span class=\"n\">get_url<\/span><span class=\"p\">().<\/span><span class=\"n\">unwrap<\/span><span class=\"p\">().<\/span><span class=\"n\">to_str<\/span><span class=\"p\">().<\/span><span class=\"n\">unwrap<\/span><span class=\"p\">().<\/span><span class=\"n\">into<\/span><span class=\"p\">();<\/span>\n<span class=\"w\">                <\/span><span class=\"n\">Content<\/span><span class=\"p\">::<\/span><span class=\"n\">Prow<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"> <\/span><span class=\"n\">pr<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">url<\/span><span class=\"w\"> <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">            <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ Read the baselines vector<\/span>\n<span class=\"w\">    <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">baselines<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"c1\">\/\/ Prepare the reader<\/span>\n<span class=\"w\">        <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">reader<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">report_reader<\/span><span class=\"p\">.<\/span><span class=\"n\">get_baselines<\/span><span class=\"p\">().<\/span><span class=\"n\">unwrap<\/span><span class=\"p\">();<\/span>\n<span class=\"w\">        <\/span><span class=\"c1\">\/\/ Read the baselines<\/span>\n<span class=\"w\">        <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"k\">mut<\/span><span class=\"w\"> <\/span><span class=\"n\">vec<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"nb\">Vec<\/span><span class=\"p\">::<\/span><span class=\"n\">with_capacity<\/span><span class=\"p\">(<\/span><span class=\"n\">reader<\/span><span class=\"p\">.<\/span><span class=\"n\">len<\/span><span class=\"p\">()<\/span><span class=\"w\"> <\/span><span class=\"k\">as<\/span><span class=\"w\"> <\/span><span class=\"kt\">usize<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">        <\/span><span class=\"k\">for<\/span><span class=\"w\"> <\/span><span class=\"n\">reader<\/span><span class=\"w\"> <\/span><span class=\"k\">in<\/span><span class=\"w\"> <\/span><span class=\"n\">reader<\/span><span class=\"p\">.<\/span><span class=\"n\">into_iter<\/span><span class=\"p\">()<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">            <\/span><span class=\"n\">vec<\/span><span class=\"p\">.<\/span><span class=\"n\">push<\/span><span class=\"p\">(<\/span><span class=\"n\">read_content<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"n\">reader<\/span><span class=\"p\">));<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">vec<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">};<\/span>\n\n<span class=\"w\">    <\/span><span class=\"n\">Report<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"> <\/span><span class=\"n\">baselines<\/span><span class=\"w\"> <\/span><span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<p>Update the decode helper:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"gu\">@@ -90,7 +142,7 @@ fn encode(report: &amp;Report, file: &amp;str) {<\/span>\n<span class=\"w\"> <\/span>fn decode(file: &amp;str) -&gt; Report {\n<span class=\"w\"> <\/span>    println!(&quot;{}: loading report&quot;, file);\n<span class=\"w\"> <\/span>    let file = File::open(file).unwrap();\n<span class=\"gd\">-    bincode::deserialize_from(file).unwrap()<\/span>\n<span class=\"gi\">+    capnp_decode(std::io::BufReader::new(file))<\/span>\n<span class=\"w\"> <\/span>}\n<\/pre><\/div>\n<p>Run the following command to demonstrate the decoding:<\/p>\n<div class=\"highlight\"><pre><span><\/span>$ cargo run -- decode report.msg\nreport.msg: loading report\ngot: Report { baselines: [Zuul { change: 42, job: &quot;test&quot;, project: &quot;demo&quot; }] }\n<\/pre><\/div>\n<p>This concludes the serialization round trip demonstration using Cap'n\nProto. In the next section I show how to update the schema.<\/p>\n<\/div>\n<div class=\"section\" id=\"evolving-the-schema\">\n<h3>Evolving the schema<\/h3>\n<p>In this section, we'll perform a schema update like we did earlier.<\/p>\n<p>Cap'n Proto prescribes a list of rules to preserve backward compability.\nFor example, it is not possible to remove fields, they can only be\nmarked as obsolete, and their memory location will always be reserved.<\/p>\n<p>It is of course possible to add new fields. For example, here is how to\nadd a title field to the report struct:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"gh\">diff --git a\/schema.capnp b\/schema.capnp<\/span>\n<span class=\"gh\">index add50b9..cd9e996 100644<\/span>\n<span class=\"gd\">--- a\/schema.capnp<\/span>\n<span class=\"gi\">+++ b\/schema.capnp<\/span>\n<span class=\"gu\">@@ -2,6 +2,7 @@<\/span>\n\n<span class=\"w\"> <\/span>struct Report {\n<span class=\"w\"> <\/span>  baselines @0 :List(Content);\n<span class=\"gi\">+  title     @1 :Text;<\/span>\n<span class=\"w\"> <\/span>}\n\n<span class=\"gh\">diff --git a\/src\/main.rs b\/src\/main.rs<\/span>\n<span class=\"gh\">index 09fc740..40411ad 100644<\/span>\n<span class=\"gd\">--- a\/src\/main.rs<\/span>\n<span class=\"gi\">+++ b\/src\/main.rs<\/span>\n<span class=\"gu\">@@ -15,6 +15,7 @@<\/span>\n<span class=\"w\"> <\/span>#[derive(Debug, Serialize, Deserialize)]\n<span class=\"w\"> <\/span>struct Report {\n<span class=\"w\"> <\/span>    baselines: Vec&lt;Content&gt;,\n<span class=\"gi\">+    title: String,<\/span>\n<span class=\"w\"> <\/span>    \/\/ list of anomaly omitted\n<span class=\"w\"> <\/span>}\n<span class=\"gu\">@@ -58,6 +58,8 @@ fn capnp_encode(report: &amp;Report, write: impl capnp::io::Write) {<\/span>\n<span class=\"w\"> <\/span>        }\n<span class=\"w\"> <\/span>    }\n\n<span class=\"gi\">+    report_builder.set_title(report.title.as_str().into());<\/span>\n\n<span class=\"w\"> <\/span>    \/\/ Write the message\n<span class=\"w\"> <\/span>    capnp::serialize::write_message(write, &amp;message).unwrap();\n<span class=\"w\"> <\/span>}\n<span class=\"gu\">@@ -111,12 +113,15 @@ fn capnp_decode(bufread: impl capnp::io::BufRead) -&gt; Report {<\/span>\n<span class=\"w\"> <\/span>        vec\n<span class=\"w\"> <\/span>    };\n\n<span class=\"gd\">-    Report { baselines }<\/span>\n<span class=\"gi\">+    let title = report_reader.get_title().unwrap().to_str().unwrap().into();<\/span>\n<span class=\"gi\">+<\/span>\n<span class=\"gi\">+    Report { baselines, title }<\/span>\n<span class=\"w\"> <\/span>}\n<span class=\"gu\">@@ -149,6 +154,7 @@ fn main() {<\/span>\n<span class=\"w\"> <\/span>    match &amp;std::env::args().collect::&lt;Vec&lt;_&gt;&gt;()[..] {\n<span class=\"w\"> <\/span>        [_, cmd, fp] if cmd == &quot;encode&quot; =&gt; {\n<span class=\"w\"> <\/span>            let report = Report {\n<span class=\"gi\">+                title: &quot;test title&quot;.to_string(),<\/span>\n<span class=\"w\"> <\/span>                baselines: vec![Content::Zuul {\n<span class=\"w\"> <\/span>                    change: 42,\n<span class=\"w\"> <\/span>                    job: &quot;test&quot;.to_string(),\n<\/pre><\/div>\n<p>Run this command to demonstrate we can read the report previously saved:<\/p>\n<div class=\"highlight\"><pre><span><\/span>$ cargo run -- decode .\/report.msg\nreport.msg: loading report\ngot: Report { baselines: [Zuul { change: 42, job: &quot;test&quot;, project: &quot;demo&quot; }], title: &quot;&quot; }\n<\/pre><\/div>\n<p>The decoding succeeded and the report title field got the default value.<\/p>\n<\/div>\n<\/div>\n<div class=\"section\" id=\"benchmark\">\n<h2>Benchmark<\/h2>\n<p>In this section, I measure the performance of Cap'n Proto using a sample\nreport of 1k lines with 2k lines of context.<\/p>\n<div class=\"section\" id=\"cpu-usage\">\n<h3>CPU usage<\/h3>\n<p>Here are the results of the <a class=\"reference external\" href=\"https:\/\/github.com\/logreduce\/logreduce\/blob\/main\/crates\/report\/benches\/bench-report.rs\">benchmark<\/a> running on my thinkpad t14\nlaptop:<\/p>\n<div class=\"highlight\"><pre><span><\/span>$ cargo bench # lower is better\nDecoder\/capnp           time:   [296.55 \u00b5s 297.00 \u00b5s 297.45 \u00b5s]\nDecoder\/bincode         time:   [278.36 \u00b5s 279.11 \u00b5s 280.01 \u00b5s]\nDecoder\/json            time:   [954.06 \u00b5s 956.90 \u00b5s 961.04 \u00b5s]\n\nEncoder\/capnp           time:   [71.704 \u00b5s 71.773 \u00b5s 71.875 \u00b5s]\nEncoder\/bincode         time:   [26.368 \u00b5s 26.394 \u00b5s 26.425 \u00b5s]\nEncoder\/json            time:   [162.20 \u00b5s 162.33 \u00b5s 162.46 \u00b5s]\n\nRead\/capnp              time:   [0.1119 \u00b5s 0.1120 \u00b5s 0.1129 \u00b5s]\nRead\/bincode            time:   [294.48 \u00b5s 295.36 \u00b5s 296.59 \u00b5s]\nRead\/json               time:   [987.78 \u00b5s 990.39 \u00b5s 995.78 \u00b5s]\n<\/pre><\/div>\n<p>Note that this is a simple benchmark, and I may have missed some\noptimizations, though the results match the public <a class=\"reference external\" href=\"https:\/\/github.com\/djkoloski\/rust_serialization_benchmark\">rust serialization\nbenchmark<\/a>.<\/p>\n<p>The encoder\/decoder benchmark loads the full report struct. Cap'n Proto\nencoder\/decoder are a bit slower because they perform extra validation\nwork to protect against malicious input (see <a class=\"reference external\" href=\"https:\/\/capnproto.org\/encoding.html#security-considerations\">security\nconsiderations<\/a>).<\/p>\n<p>The read benchmark traverses the report to count the number of lines. In\nthat case, Cap'n Proto is three orders of magnitude faster because we\ncan access the data directly from the reading buffer, without perfoming\nany copy. This is great for rendering in the browser, because the dom\nelements need to copy the data anyway, so we can avoid decoding the\nreport into an intermediary structure. Here is how the read benchmark is\nimplemented:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"n\">group<\/span><span class=\"p\">.<\/span><span class=\"n\">bench_function<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;capnp&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"o\">|<\/span><span class=\"n\">b<\/span><span class=\"o\">|<\/span><span class=\"w\"> <\/span><span class=\"n\">b<\/span><span class=\"p\">.<\/span><span class=\"n\">iter<\/span><span class=\"p\">(<\/span><span class=\"o\">||<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ Create a message reader<\/span>\n<span class=\"w\">    <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"k\">mut<\/span><span class=\"w\"> <\/span><span class=\"n\">slice<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"kp\">&amp;<\/span><span class=\"p\">[<\/span><span class=\"kt\">u8<\/span><span class=\"p\">]<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">black_box<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"n\">encoded_capnp<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">message_reader<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">capnp<\/span><span class=\"p\">::<\/span><span class=\"n\">serialize<\/span><span class=\"p\">::<\/span><span class=\"n\">read_message_from_flat_slice<\/span><span class=\"p\">(<\/span>\n<span class=\"w\">        <\/span><span class=\"o\">&amp;<\/span><span class=\"k\">mut<\/span><span class=\"w\"> <\/span><span class=\"n\">slice<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">capnp<\/span><span class=\"p\">::<\/span><span class=\"n\">message<\/span><span class=\"p\">::<\/span><span class=\"n\">ReaderOptions<\/span><span class=\"p\">::<\/span><span class=\"n\">new<\/span><span class=\"p\">(),<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">)<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">.<\/span><span class=\"n\">unwrap<\/span><span class=\"p\">();<\/span>\n<span class=\"w\">    <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">reader<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">message_reader<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">.<\/span><span class=\"n\">get_root<\/span><span class=\"p\">::<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">logreduce_report<\/span><span class=\"p\">::<\/span><span class=\"n\">schema_capnp<\/span><span class=\"p\">::<\/span><span class=\"n\">report<\/span><span class=\"p\">::<\/span><span class=\"n\">Reader<\/span><span class=\"o\">&lt;&#39;<\/span><span class=\"nb\">_<\/span><span class=\"o\">&gt;&gt;<\/span><span class=\"p\">()<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">.<\/span><span class=\"n\">unwrap<\/span><span class=\"p\">();<\/span>\n\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ Traverse the list of log reports<\/span>\n<span class=\"w\">    <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">count<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">reader<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">.<\/span><span class=\"n\">get_log_reports<\/span><span class=\"p\">()<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">.<\/span><span class=\"n\">unwrap<\/span><span class=\"p\">()<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">.<\/span><span class=\"n\">iter<\/span><span class=\"p\">()<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">.<\/span><span class=\"n\">fold<\/span><span class=\"p\">(<\/span><span class=\"mi\">0<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"o\">|<\/span><span class=\"n\">acc<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">lr<\/span><span class=\"o\">|<\/span><span class=\"w\"> <\/span><span class=\"n\">acc<\/span><span class=\"w\"> <\/span><span class=\"o\">+<\/span><span class=\"w\"> <\/span><span class=\"n\">lr<\/span><span class=\"p\">.<\/span><span class=\"n\">get_anomalies<\/span><span class=\"p\">().<\/span><span class=\"n\">unwrap<\/span><span class=\"p\">().<\/span><span class=\"n\">len<\/span><span class=\"p\">());<\/span>\n<span class=\"w\">    <\/span><span class=\"fm\">assert_eq!<\/span><span class=\"p\">(<\/span><span class=\"n\">count<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"mi\">1025<\/span><span class=\"p\">);<\/span>\n<span class=\"p\">}));<\/span>\n\n<span class=\"n\">group<\/span><span class=\"p\">.<\/span><span class=\"n\">bench_function<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;bincode&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"o\">|<\/span><span class=\"n\">b<\/span><span class=\"o\">|<\/span><span class=\"w\"> <\/span><span class=\"n\">b<\/span><span class=\"p\">.<\/span><span class=\"n\">iter<\/span><span class=\"p\">(<\/span><span class=\"o\">||<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">slice<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"kp\">&amp;<\/span><span class=\"p\">[<\/span><span class=\"kt\">u8<\/span><span class=\"p\">]<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">black_box<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"n\">encoded_bincode<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">report<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nc\">Report<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">bincode<\/span><span class=\"p\">::<\/span><span class=\"n\">deserialize_from<\/span><span class=\"p\">(<\/span><span class=\"n\">slice<\/span><span class=\"p\">).<\/span><span class=\"n\">unwrap<\/span><span class=\"p\">();<\/span>\n<span class=\"w\">    <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">count<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">report<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">.<\/span><span class=\"n\">log_reports<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">.<\/span><span class=\"n\">iter<\/span><span class=\"p\">()<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">.<\/span><span class=\"n\">fold<\/span><span class=\"p\">(<\/span><span class=\"mi\">0<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"o\">|<\/span><span class=\"n\">acc<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">lr<\/span><span class=\"o\">|<\/span><span class=\"w\"> <\/span><span class=\"n\">acc<\/span><span class=\"w\"> <\/span><span class=\"o\">+<\/span><span class=\"w\"> <\/span><span class=\"n\">lr<\/span><span class=\"p\">.<\/span><span class=\"n\">anomalies<\/span><span class=\"p\">.<\/span><span class=\"n\">len<\/span><span class=\"p\">());<\/span>\n<span class=\"w\">    <\/span><span class=\"fm\">assert_eq!<\/span><span class=\"p\">(<\/span><span class=\"n\">count<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"mi\">1025<\/span><span class=\"p\">)<\/span>\n<span class=\"p\">}));<\/span>\n<\/pre><\/div>\n<\/div>\n<div class=\"section\" id=\"report-file-size-1\">\n<span id=\"report-file-size\"><\/span><h3>Report file size.<\/h3>\n<p>Cap'n Proto wire format is a bit heavier and after compression, about\n12% bigger than bincode:<\/p>\n<div class=\"highlight\"><pre><span><\/span>$ du -b report*\n162824  report-capnp.bin\n114360  report-capnp-packed.bin\n123916  report-bincode.bin\n149830  report.json\n\n$ gzip report*; du -b report*\n59361   report-capnp.bin.gz\n61280   report-capnp-packed.bin.gz\n52435   report-bincode.bin.gz\n50401   report.json.gz\n<\/pre><\/div>\n<p>Note that Cap'n Proto also supports a packed format, but it has higher\nruntime costs and worse gzip compressions.<\/p>\n<p>It is surprising that compression works so well on JSON for this schema.\nI guess this is because the report is mostly a list of list of text with\nfew structure fields.<\/p>\n<\/div>\n<div class=\"section\" id=\"client-code-size\">\n<h3>Client code size<\/h3>\n<p>Lastly the runtime code is similar, here is the WASM size before and\nafter the <a class=\"reference external\" href=\"https:\/\/github.com\/logreduce\/logreduce\/pull\/57\">PR introducing capnp<\/a>:<\/p>\n<div class=\"highlight\"><pre><span><\/span>$ nix build -o capnp   github:logreduce\/logreduce\/fb4f69e#web\n$ nix build -o bincode github:logreduce\/logreduce\/2578019#web\n$ du -b capnp\/*.wasm bincode\/*.wasm\n529322  capnp\/logreduce-web.wasm\n531327  bincode\/logreduce-web.wasm\n<\/pre><\/div>\n<p>I guess the runtime code is smaller because capnp does not use the serde\nmachinery.<\/p>\n<\/div>\n<\/div>\n<div class=\"section\" id=\"conclusion\">\n<h2>Conclusion<\/h2>\n<p>Cap'n Proto works well for logreduce. The schema language is simple to\nunderstand and the generated code is easy to work with. Being able to\nread the data directly from memory is a great capability that can enable\nblazingly fast processing.<\/p>\n<p>Writing the encoder and decoder is a bit of fairly mechanical work.\nHowever doing this work manually enables adding customization, for\nexample, deduplicating the data using a process known as <a class=\"reference external\" href=\"https:\/\/en.wikipedia.org\/wiki\/String_interning\">string\ninterning<\/a>. Future work in rust introspection may enable deriving this\nwork automatically, checkout the <a class=\"reference external\" href=\"https:\/\/soasis.org\/posts\/a-mirror-for-rust-a-plan-for-generic-compile-time-introspection-in-rust\/\">Shepherd\u2019s Oasis blog post<\/a> to learn\nmore.<\/p>\n<p>In conclusion, replacing bincode with Cap'n Proto future proofs\nlogreduce reports. This format adds some negligible storage and\nprocessing costs, in exchange for a backward compatible schema and more\nefficient data access. Flatbuffers is also worth considering as it has a\nlower storage cost, but it requires more work to verify that the data is\nsafe to be processed.<\/p>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2023 Sep 29 to Oct 18 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2023-sep-29-to-oct-18-summary.html","rel":"alternate"}},"published":"2023-10-18T10:00:00+00:00","updated":"2023-10-18T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2023-10-18:\/sprint-2023-sep-29-to-oct-18-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We attempted to patch nodepool to add low level API metrics with kubernetes-based providers. The approach was rejected but we have alternatives we can explore<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<p>Software Factory<\/p>\n<ul class=\"simple\">\n<li>We proposed an ADR to overhaul the CLI: <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29780\/1\/doc\/adr\/0011-CLI-overhaul.md\">https \u2026<\/a><\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We attempted to patch nodepool to add low level API metrics with kubernetes-based providers. The approach was rejected but we have alternatives we can explore<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<p>Software Factory<\/p>\n<ul class=\"simple\">\n<li>We proposed an ADR to overhaul the CLI: <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29780\/1\/doc\/adr\/0011-CLI-overhaul.md\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29780\/1\/doc\/adr\/0011-CLI-overhaul.md<\/a><\/li>\n<li>We can automatically monitor OpenStack API calls from nodepool on a per cloud basis, if nodepool's clouds.yaml is properly configured.<\/li>\n<li>We've enabled doc publishing on github pages with a github action. The doc is updated with every commit at <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.github.io\/sf-operator\/\">https:\/\/softwarefactory-project.github.io\/sf-operator\/<\/a> - API doc autogeneration is also coming: <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29796\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29796<\/a><\/li>\n<li>We've added Nodepool Builder<\/li>\n<li>We've added Zuul Merger<\/li>\n<li>We've change the domain name from sftests.com to sfop.me<\/li>\n<li>We've added openstack, aws and kubectl cli's to nodepool images<\/li>\n<li>We bumped Nodepool to 9.0.0-3<\/li>\n<li>We've added Zuul Log Levels to CRD<\/li>\n<li>We've added GitHub connection support to Zuul connections<\/li>\n<li>We've started to migrate sf-operator-olm job from sf.io(containers base instance) to micro.sf.io(kubernetes base instance)<\/li>\n<li>We added a ssh key pair for nodepool-builder pod + check that we can use an external builder <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29589\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29589<\/a><\/li>\n<li>We added capability to set the nodepool-builder home volume size <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29616\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29616<\/a><\/li>\n<li>We added log level setting <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29617\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29617<\/a><\/li>\n<li>We added sf-operator documenation for nodepool-builder <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29634\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29634<\/a><\/li>\n<li>We ensured nodepool builder holds the cloud and kube secrets as well <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29650\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29650<\/a><\/li>\n<li>We populated a proposal for the milestone 4<\/li>\n<li>We explored MP+ dev \/ started to make use of it \/ requested the Monocle operator installation in IT ticket<\/li>\n<li>We validated the run of Monocle on MP+ dev (No Routes) and waiting for the operator itself to be installed<\/li>\n<li>We added an httpd container to make nodepool builder build logs browsable <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29763\/\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29763\/<\/a><\/li>\n<li>We added the zuul pub key to nodepool-builder <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29786\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29786<\/a><\/li>\n<li>We added the capability to resize nodepool-builder \/var\/lib\/nodepool <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29785\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29785<\/a><\/li>\n<li>We are adding the cloud-centos-9 build recipe to nodepool.microshift.sf.io <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/plugins\/gitiles\/microzuul-config\/+\/refs\/heads\/master\/nodepool\/dib-ansible\/cloud-centos-9.yaml\">https:\/\/softwarefactory-project.io\/r\/plugins\/gitiles\/microzuul-config\/+\/refs\/heads\/master\/nodepool\/dib-ansible\/cloud-centos-9.yaml<\/a><\/li>\n<li>We added blog post about etcd issue and sf-operator<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Operators and Monitoring: making life easier for deployers","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/operators-and-monitoring-making-life-easier-for-deployers.html","rel":"alternate"}},"published":"2023-10-17T00:00:00+00:00","updated":"2023-10-17T00:00:00+00:00","author":{"name":"Matthieu Huin"},"id":"tag:www.softwarefactory-project.io,2023-10-17:\/operators-and-monitoring-making-life-easier-for-deployers.html","summary":"<p>In this article, I will share my thoughts, feedback and ideas following the <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/q\/(topic:prometheus_operator+OR+topic:monitoring)+project:software-factory\/sf-operator\">work I have done\non monitoring operands<\/a> for the <a class=\"reference external\" href=\"https:\/\/github.com\/softwarefactory-project\/sf-operator\">SF Operator<\/a>, in the hopes that other developers looking to add deep\ninsights and automated service tuning to their own operators may build upon my experience.<\/p>\n<div class=\"section\" id=\"if-you-can-t-measure-it-you-can-t-size-it-properly\">\n<h2>If you \u2026<\/h2><\/div>","content":"<p>In this article, I will share my thoughts, feedback and ideas following the <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/q\/(topic:prometheus_operator+OR+topic:monitoring)+project:software-factory\/sf-operator\">work I have done\non monitoring operands<\/a> for the <a class=\"reference external\" href=\"https:\/\/github.com\/softwarefactory-project\/sf-operator\">SF Operator<\/a>, in the hopes that other developers looking to add deep\ninsights and automated service tuning to their own operators may build upon my experience.<\/p>\n<div class=\"section\" id=\"if-you-can-t-measure-it-you-can-t-size-it-properly\">\n<h2>If you can't measure it, you can't size it properly<\/h2>\n<p>Orchestrating applications with Kubernetes opens up a world of possibilities. Among the biggest game changers,\naccording to me, we have:<\/p>\n<ul class=\"simple\">\n<li>Upgrade strategies that also simplify rollback should a problem arise<\/li>\n<li>Horizontal scaling and load balancing your workload, two often dreaded Ops tasks (I know I do!), become much simpler\nto handle. More often than not, it's just about changing the replica count in a manifest; your\ncluster handles the rest under the hood.<\/li>\n<\/ul>\n<p>Scaling up or down, however, requires knowledge of <strong>when<\/strong> it should occur. While Kubernetes' <a class=\"reference external\" href=\"https:\/\/kubernetes.io\/docs\/tasks\/run-application\/horizontal-pod-autoscale\/\">Horizontal Pod Autoscaling<\/a>\ncan trigger scaling on CPU or memory usage automatically, application deployers with deeper knowledge of\ntheir software may want to react on more precise events that can be measured. And that is where monitoring embedded\ninto an operator comes into play.<\/p>\n<p>Operator developers can define their Pods to include a way to emit metrics. They can also use the operator's\ncontrollers to configure metrics collection, so that a Prometheus instance will know automatically how to scrape these\nmetrics. Finally, with operations knowledge, the operator can include interesting alerts that will\ntrigger when the application operates outside of its expected behavior.<\/p>\n<p>And when you deploy an application with such an operator, you get all that operating knowledge for free!<\/p>\n<\/div>\n<div class=\"section\" id=\"the-prometheus-operator\">\n<h2>The Prometheus Operator<\/h2>\n<p>The <a class=\"reference external\" href=\"https:\/\/prometheus-operator.dev\">prometheus operator<\/a>, unsurprisingly, is truly the cornerstone of enabling monitoring with\noperators. It provides a declarative API (ie &quot;give me a Prometheus instance!&quot;, or &quot;monitor this pod!&quot;)\nthat makes it really simple to set up a monitoring environment and work with monitoring resources in\nan operator's source code.<\/p>\n<p>I would recommend installing the prometheus operator on any Kubernetes cluster that will run\napplications. You can then spin up a Prometheus instance that will collect metrics emitted on a given namespace\nand\/or from resources matching specific labels.<\/p>\n<p>On OpenShift, the prometheus operator can optionally be installed at deployment time,\nwhich will result in a cluster-wide instance of Prometheus that can collect application metrics automatically.<\/p>\n<\/div>\n<div class=\"section\" id=\"exposing-your-operands-metrics\">\n<h2>Exposing your operands' metrics<\/h2>\n<p>In the development of the SF-Operator, we face three categories of operands when it comes to metrics:<\/p>\n<ul class=\"simple\">\n<li>The operand's underlying application(s) emit prometheus metrics<\/li>\n<li>The operand's underlying application(s) do not emit relevant metrics, and we desire Pod-related metrics<\/li>\n<li>The operand's underlying application(s) emit statsD metrics<\/li>\n<\/ul>\n<p>Let's dive into the details of each case.<\/p>\n<div class=\"section\" id=\"the-operand-emits-prometheus-metrics\">\n<h3>The Operand emits prometheus metrics<\/h3>\n<p>This is the case for Zuul. It is truly the simplest case since it is enough to:<\/p>\n<ul class=\"simple\">\n<li><a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28612\/4\/controllers\/zuul.go#425\">ensure emitting the metrics is enabled in the operand's configuration<\/a><\/li>\n<li><a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29527\/4\/controllers\/zuul.go#212\">ensure the right port is declared in the relevant container spec<\/a><\/li>\n<\/ul>\n<p>We could also add a route to enable an external Prometheus to scrape the metrics endpoint,\nbut since we target OpenShift we make the assumption that a Prometheus instance that is internal\nto the cluster will be used.<\/p>\n<\/div>\n<div class=\"section\" id=\"the-operand-doesn-t-emit-relevant-metrics-and-we-desire-pod-related-metrics\">\n<h3>The Operand doesn't emit relevant metrics, and we desire Pod-related metrics<\/h3>\n<p>This is the case with the Log server. Basically, this operand is just an Apache server and an SSH server taped together\non top of storage. We <strong>could<\/strong> look into <a class=\"reference external\" href=\"https:\/\/www.giffgaff.io\/tech\/monitoring-apache-with-prometheus\">emitting Apache metrics to be scraped by Prometheus<\/a>, but from years of\noperating several large Software Factories, we know for a fact that SSH and HTTPD performances are nearly never bottlenecks\nin our use cases.<\/p>\n<p>What we <strong>do want<\/strong> to keep an eye on, however, is disk usage, and down the line be notified when available space\nis below 10% of total capacity. When testing on MicroShift, I never actually managed\nto collect kubelet metrics that are supposed to expose statistics on persistent volumes being used. This is why I\nopted to expose disk usage metrics with a sidecar container running <a class=\"reference external\" href=\"https:\/\/github.com\/prometheus\/node_exporter#node-exporter\">Node Exporter<\/a>. Slap that container onto\nyour Pod, and voil\u00e0! You're basically back to case 1.<\/p>\n<p>You can see how it is implemented in the SF-operator as a <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29391\/37\/controllers\/libs\/monitoring\/monitoring.go#50\">helper function called &quot;MkNodeExporterSideCarContainer&quot;<\/a>,\nand <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29391\/37\/controllers\/logserver_controller.go#348\">within the Log server controller<\/a>.<\/p>\n<\/div>\n<div class=\"section\" id=\"the-operand-emits-statsd-metrics\">\n<h3>The Operand emits statsD metrics<\/h3>\n<p>This is the case with Nodepool and Zuul. For simplicity's sake, we would like to aggregate all metrics in Prometheus.\nThis can be done easily with a sidecar container running <a class=\"reference external\" href=\"https:\/\/github.com\/prometheus\/statsd_exporter#overview\">StatsD Exporter<\/a>. All you need is a <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29482\">mapping configuration file<\/a>\nthat will tell the exporter how to translate statsD metrics into prometheus metrics - especially where the labels are\nin the original metric's name. Once again, all you need then is to expose the exporter's service port and your metrics are\nready to be scraped.<\/p>\n<p>Like for Node Exporter, we created a <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29391\/37\/controllers\/libs\/monitoring\/monitoring.go#93\">helper function called &quot;MkStatsdExporterSideCarContainer&quot;<\/a> that makes it easy\nto emit statsd metrics from a Pod in a Prometheus-friendly format.<\/p>\n<\/div>\n<\/div>\n<div class=\"section\" id=\"making-sure-the-metrics-will-be-collected\">\n<h2>Making sure the metrics will be collected<\/h2>\n<p>In the last paragraph, we made sure our metrics can be scraped from our Pods. Thanks to the prometheus operator, we can\ngo one step further and tell <em>any<\/em> Prometheus instance running on the cluster how to pick these metrics up.<\/p>\n<p>The prometheus operator defines the <a class=\"reference external\" href=\"https:\/\/github.com\/prometheus-operator\/prometheus-operator\/blob\/main\/Documentation\/api.md#podmonitor\">PodMonitor<\/a> and the <a class=\"reference external\" href=\"https:\/\/github.com\/prometheus-operator\/prometheus-operator\/blob\/main\/Documentation\/api.md#servicemonitor\">ServiceMonitor<\/a> custom resources that, as their names suggest,\nwill define how to monitor a given pod or service. Since as I said earlier, we didn't deem necessary to create services\nfor each monitoring-related port, we <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29391\/37\/controllers\/libs\/monitoring\/monitoring.go#176\">opted to manage PodMonitors in the SF-Operator<\/a>. All you need is to specify the\n&quot;monitoring&quot; ports' names to scrape on the Pod, and set a label selector (in our case, every PodMonitor related to\na SF deployment will have a label called <tt class=\"docutils literal\"><span class=\"pre\">sf-monitoring<\/span><\/tt> set to the name of the monitored application).<\/p>\n<p>If a cluster-wide Prometheus instance exists, for example if you're using an OpenShift cluster with this feature enabled,\nyou can then access metrics from your SF deployment as soon as it is deployed. Otherwise you can use the <cite>sfconfig prometheus<\/cite>\nCLI command to deploy a tenant-scoped Prometheus instance with the proper label selector configured to scrape only\nSF-issued metrics.<\/p>\n<\/div>\n<div class=\"section\" id=\"injecting-monitoring-knowledge-into-the-operator\">\n<h2>Injecting monitoring knowledge into the operator<\/h2>\n<p>So far, we've seen how deploying our application with an operator allowed us to also pre-configure the monitoring stack.\nWe're emitting metrics and collecting them, but what should we do with this window on our system?<\/p>\n<p>We should, obviously, define alerts so that we can know when the application is not running optimally, or worse. And as\nyou probably guessed already, there's a prometheus-operator defined Custom Resource for that: the <a class=\"reference external\" href=\"https:\/\/github.com\/prometheus-operator\/prometheus-operator\/blob\/main\/Documentation\/api.md#prometheusrule\">PrometheusRule<\/a>.<\/p>\n<p>The resource is very straightforward to use, <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29370\/20\/controllers\/logserver_controller.go#121\">as can be seen in the log server controller<\/a> for example. Once again,\nwe scope our PrometheusRules to the <tt class=\"docutils literal\"><span class=\"pre\">sf-monitoring<\/span><\/tt> label and they will be picked up automatically by the right Prometheus\ninstance.<\/p>\n<p>What's great is that with these rules, developers of an operator can inject their knowledge and expertise about an application's\nexpected behavior. My team and I have been running Zuul and Nodepool at scale for several large deployments for years,\nso we know a thing or two about what's interesting to monitor and what should warrant immediate remediation action.\nNow we can easily add this knowledge in a way that future deployers can benefit from almost immediately.<\/p>\n<img alt=\"\" src=\"images\/itsbeautiful.jpeg\" \/>\n<\/div>\n<div class=\"section\" id=\"next-steps\">\n<h2>Next steps<\/h2>\n<p>At the time of this writing, the base foundations of the monitoring stack in SF-Operator have just landed in the code\nbase. Now that this is over with, I'd like to experiment further with the following:<\/p>\n<div class=\"section\" id=\"operator-metrics\">\n<h3>Operator metrics<\/h3>\n<p>The <a class=\"reference external\" href=\"https:\/\/book.kubebuilder.io\/reference\/metrics\">kubebuilder documentation about metrics<\/a> explains how to publish default performance metrics\nfor each controller in an operator. It is also possible to add and emit custom metrics.<\/p>\n<p>On a purely operational level, these metrics are less interesting to us than operands metrics. However, it would\nprobably be good to keep an eye on ticks on <a class=\"reference external\" href=\"https:\/\/github.com\/kubernetes-sigs\/controller-runtime\/blob\/v0.11.0\/pkg\/internal\/controller\/metrics\/metrics.go#L37\">controller_runtime_reconcile_errors_total<\/a> and\non the evolution of <a class=\"reference external\" href=\"https:\/\/github.com\/kubernetes-sigs\/controller-runtime\/blob\/v0.11.0\/pkg\/internal\/controller\/metrics\/metrics.go#L44\">controller_runtime_reconcile_time_seconds<\/a> for performance fluctuations.<\/p>\n<\/div>\n<div class=\"section\" id=\"keda\">\n<h3>KEDA<\/h3>\n<p>This is where the fun begins! The <a class=\"reference external\" href=\"https:\/\/keda.sh\">KEDA operator<\/a> greatly expands the capabilities of Kubernetes' Horizontal Pod Autoscaler.\nWhile HPA relies on basic metrics like Pod CPU or memory use (or requires some additional effort to work with custom metrics),\nKEDA allows you to trigger your autoscaling with a lot more event types.<\/p>\n<p>And among them... <a class=\"reference external\" href=\"https:\/\/keda.sh\/docs\/2.12\/scalers\/prometheus\/\">Prometheus queries<\/a>.<\/p>\n<p>We could provide predefined KEDA triggers based on relevant queries like <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29682\/1\/controllers\/zuul.go#420\">NotEnoughExecutors<\/a> to start spawning\nnew executors when this alert fires.<\/p>\n<\/div>\n<div class=\"section\" id=\"log-server-autoresize\">\n<h3>Log server autoresize<\/h3>\n<p>So far we have only considered metrics-driven scaling of <strong>pods<\/strong> horizontally. This works especially well for stateless applications, or\nstateful applications that have a strategy to configure the first deployed pod as a primary node or master, and every extra pod as a replica or slave.\nBut the log server application isn't stateless (logs are stored) and a primary\/replicas architecture would be hard, if not impossible, to implement correctly with HTTPD <strong>and<\/strong>\nSSH. And as stated before, Apache and SSH are virtually never bottlenecks for the Log server; but <em>storage<\/em> is. Kubernetes, and OpenShift as well for that\nmatter, do not seem to address this need for storage autoscaling.<\/p>\n<p>But since we deploy the Log server via an operator, it might be possible to circumvent this limitation like so:<\/p>\n<ul class=\"simple\">\n<li>in the Log server controller's reconcile loop, use the RESTClient library or some other way to query the <tt class=\"docutils literal\">\/metrics<\/tt> endpoint on the node exporter sidecar, or simply run <tt class=\"docutils literal\">du<\/tt> or similar<\/li>\n<li>compute how much free space is available<\/li>\n<li>if the value is under 10% for a given period, increase the log server's persistent volume's size by a predefined increment<\/li>\n<li>reconcile again later to check free space and repeat<\/li>\n<\/ul>\n<p>If these experimentations are successful, the day to day operation of our Zuul deployments is going to be <strong>so<\/strong> much easier!<\/p>\n<\/div>\n<\/div>\n<div class=\"section\" id=\"conclusion\">\n<h2>Conclusion<\/h2>\n<p>I must say that working with the operator framework and monitoring, while a bit scary initially, is starting to make so much sense in the long run, and is even\nbeginning to feel exciting, considering all the open possibilities to make the operations side of my work much easier.<\/p>\n<p>I feel like orchestration with Kubernetes and OpenShift is to managing applications what packaging RPMs has been to installing said applications: a lot of effort for\npackagers and operator developers, but deployers' lives are made so much easier for it. Kubernetes and OpenShift take it to the next level by adding the opportunity\nto inject lifecycle and management &quot;intelligence&quot;, leading potentially to applications being able to &quot;auto-pilot&quot;, freeing your time to focus on the really cool stuff.<\/p>\n<p>I am really looking forward to experimenting and discovering more of what operators can offer.<\/p>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"SF Operator - how hardware is important in Kubernetes","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sf-operator-how-hardware-is-important-in-kubernetes.html","rel":"alternate"}},"published":"2023-10-16T00:00:00+00:00","updated":"2023-10-16T00:00:00+00:00","author":{"name":"dpawlik"},"id":"tag:www.softwarefactory-project.io,2023-10-16:\/sf-operator-how-hardware-is-important-in-kubernetes.html","summary":"<div class=\"section\" id=\"problem-description-1\">\n<span id=\"problem-description\"><\/span><h2>Problem description<\/h2>\n<p>As you know, the future release of the Software Factory project will be based on the\nKubernetes deployment. On adding new services, there are more and more pods\nspawned on the Kubernetes cluster, which might raise some complications, when\nthe test environment is limited.\nAfter a while, we \u2026<\/p><\/div>","content":"<div class=\"section\" id=\"problem-description-1\">\n<span id=\"problem-description\"><\/span><h2>Problem description<\/h2>\n<p>As you know, the future release of the Software Factory project will be based on the\nKubernetes deployment. On adding new services, there are more and more pods\nspawned on the Kubernetes cluster, which might raise some complications, when\nthe test environment is limited.\nAfter a while, we spotted an issue, the CI jobs were failing for an unknown\nreason - sometimes a test was checking if the API is up and received a HTTP 503 error,\nsometimes pods were marked as <cite>Running<\/cite>, but were actually not ready. All of those\nerrors were related to <cite>ReadinessProbe<\/cite>, <cite>StartupProbe<\/cite> or <cite>LivenessProbe<\/cite>,\nbut from time to time we had the following error:<\/p>\n<div class=\"highlight\"><pre><span><\/span>panic:<span class=\"w\"> <\/span>Could<span class=\"w\"> <\/span>not<span class=\"w\"> <\/span>create<span class=\"w\"> <\/span><span class=\"p\">&amp;<\/span>Secret<span class=\"o\">{<\/span>ObjectMeta:<span class=\"o\">{<\/span>nodepool-providers-secrets\nsf<span class=\"w\">    <\/span><span class=\"m\">0<\/span><span class=\"w\"> <\/span><span class=\"m\">0001<\/span>-01-01<span class=\"w\"> <\/span><span class=\"m\">00<\/span>:00:00<span class=\"w\"> <\/span>+0000<span class=\"w\"> <\/span>UTC<span class=\"w\"> <\/span>&lt;nil&gt;<span class=\"w\"> <\/span>&lt;nil&gt;<span class=\"w\"> <\/span>map<span class=\"o\">[]<\/span><span class=\"w\"> <\/span>map<span class=\"o\">[]<\/span><span class=\"w\"> <\/span><span class=\"o\">[]<\/span><span class=\"w\"> <\/span><span class=\"o\">[]<\/span><span class=\"w\"> <\/span><span class=\"o\">[]}<\/span>,\nData:map<span class=\"o\">[<\/span>string<span class=\"o\">][]<\/span>byte<span class=\"o\">{<\/span>kube.config:<span class=\"w\"> <\/span><span class=\"o\">[<\/span><span class=\"m\">97<\/span><span class=\"w\"> <\/span><span class=\"m\">112<\/span><span class=\"w\"> <\/span><span class=\"m\">105<\/span><span class=\"w\"> <\/span><span class=\"m\">86<\/span><span class=\"w\"> <\/span><span class=\"m\">101<\/span><span class=\"w\"> <\/span><span class=\"m\">114<\/span><span class=\"w\"> <\/span><span class=\"m\">115<\/span><span class=\"w\"> <\/span><span class=\"m\">105<\/span><span class=\"w\"> <\/span><span class=\"o\">(<\/span>...<span class=\"o\">)<\/span><span class=\"w\"> <\/span><span class=\"m\">10<\/span><span class=\"o\">]<\/span>,<span class=\"o\">}<\/span>,\nType:,StringData:map<span class=\"o\">[<\/span>string<span class=\"o\">]<\/span>string<span class=\"o\">{}<\/span>,Immutable:nil,<span class=\"o\">}<\/span>:<span class=\"w\"> <\/span>etcdserver:<span class=\"w\"> <\/span>request<span class=\"w\"> <\/span>timed<span class=\"w\"> <\/span>out\n<\/pre><\/div>\n<p>Initially the isssue occured rarely, but after a while, it happens more and more\noften.<\/p>\n<\/div>\n<div class=\"section\" id=\"what-is-etcd\">\n<h2>What is etcd?<\/h2>\n<p>The <cite>etcd<\/cite> service is the &quot;heart&quot; of Kubernetes. It is: &quot;A distributed,\nreliable key-value store for the most critical data of a distributed system&quot; <a class=\"reference external\" href=\"https:\/\/etcd.io\/\">source<\/a>.\nThat service keeps track of the state of the whole Kubernetes cluster, its configuration,\nservice statuses, and others. That service needs to be working without any\nissue or other components will be impacted and might not run properly.<\/p>\n<p>As it was described in the error message above in the &quot;<a class=\"reference internal\" href=\"#problem-description\">Problem_description<\/a>&quot; paragraph,\nthe error: <cite>etcdserver: request timed out<\/cite> might suggest that we had an issue\nwith the &quot;core&quot; service of Kubernetes, so we performed a few tests to see why the\n<cite>etcd<\/cite> is not working properly in our CI jobs.<\/p>\n<\/div>\n<div class=\"section\" id=\"testing-environment\">\n<h2>Testing environment<\/h2>\n<p>Most of our CI jobs related to the sf-operator project are spawning on a VM\nflavor with the following specs:<\/p>\n<ul class=\"simple\">\n<li>8 GB RAM<\/li>\n<li>40 GB HDD disk<\/li>\n<li>8 vCPUs<\/li>\n<\/ul>\n<p>which is a very small part of the physical resources of the hypervisor.\nAfter pushing new changes to the Zuul CI, it can happen that all new jobs will\nbe running on the same L0 host (that's how the cloud is working: instance\nis spawned on the hypervisor with &quot;best score&quot;, so normally it should have\nvery good performance. It can happen, that all compute hosts are overloaded,\nso the OpenStack Nova Scheduler will choose a host that has best score from\nall overloaded hosts, so it may affect the etcd perofmance), especially that\nsome parts of the CI jobs are repeating:<\/p>\n<ul class=\"simple\">\n<li>deploy Microshift,<\/li>\n<li>build an image,<\/li>\n<li>deploy services: it is pulling new images, storing them on the disk, etc.<\/li>\n<\/ul>\n<p>It might happen that, at the same time, there can be many jobs doing the same operation, so\nthat can take all disk resources, such as IO or R\/W operations.<\/p>\n<div class=\"section\" id=\"microshift\">\n<h3>MicroShift<\/h3>\n<p>We are using the MicroShift tool for deploying the sf-operator. &quot;MicroShift\nis an experimental flavor of OpenShift\/Kubernetes optimized for the device edge.\nIt targets the niche between minimal, standalone Linux edge devices and\nfull-fledged OpenShift\/Kubernetes edge clusters&quot;. <a class=\"reference external\" href=\"https:\/\/next.redhat.com\/project\/microshift\/\">source<\/a>.<\/p>\n<p>To deploy MicroShift, we developed an Ansible role, which you can find\n<a class=\"reference external\" href=\"https:\/\/github.com\/openstack-k8s-operators\/ansible-microshift-role\">here<\/a> on Github.<\/p>\n<\/div>\n<\/div>\n<div class=\"section\" id=\"is-it-hardware\">\n<h2>Is it hardware?<\/h2>\n<p>Let's have a theory: what if the disk is having troubles in our CI?\nFirst, what we check is how the monitoring metrics look for a specific cloud\nprovider host (L0), on which the job was performed, and it raises an error.<\/p>\n<img alt=\"diskUsage\" src=\"images\/etcd\/grafana.jpg\" \/>\n<p>The Grafana visualization shows that the disk IO is near 1K, which is very\nlow for modern servers. A similar situation exists for the R\/W operation: both are\nalso low, but what if other operations done on the disk might affect etcd work?\nAccording to our cloud provider, our hypervisor's storage consists\nof 4 Intel 960 SSD disks mounted in software RAID10.<\/p>\n<p>This caused us to have mixed feelings about whether the issue is really in the disk.\nSo we decided to make some benchmarks.<\/p>\n<\/div>\n<div class=\"section\" id=\"benchmarking-servers\">\n<h2>Benchmarking servers<\/h2>\n<p>There are many tools that can check your disk's performance. Our focus was on\ntwo basic tests:<\/p>\n<ul class=\"simple\">\n<li>fio<\/li>\n<li>phoronix tests suite<\/li>\n<li>etcd benchmark tool<\/li>\n<\/ul>\n<div class=\"section\" id=\"fio-tool\">\n<h3>Fio tool<\/h3>\n<p>How we test:<\/p>\n<div class=\"highlight\"><pre><span><\/span>curl<span class=\"w\"> <\/span>-LO<span class=\"w\"> <\/span>https:\/\/github.com\/rancherlabs\/support-tools\/raw\/master\/instant-fio-master\/instant-fio-master.sh\nbash<span class=\"w\"> <\/span>instant-fio-master.sh\n\n<span class=\"nb\">export<\/span><span class=\"w\"> <\/span><span class=\"nv\">PATH<\/span><span class=\"o\">=<\/span>\/usr\/local\/bin:<span class=\"nv\">$PATH<\/span>\nmkdir<span class=\"w\"> <\/span>test-data\nfio<span class=\"w\"> <\/span>--rw<span class=\"o\">=<\/span>write<span class=\"w\"> <\/span>--ioengine<span class=\"o\">=<\/span>sync<span class=\"w\"> <\/span>--fdatasync<span class=\"o\">=<\/span><span class=\"m\">1<\/span><span class=\"w\"> <\/span>--directory<span class=\"o\">=<\/span>test-data<span class=\"w\"> <\/span>--size<span class=\"o\">=<\/span>100m<span class=\"w\"> <\/span>--bs<span class=\"o\">=<\/span><span class=\"m\">2300<\/span><span class=\"w\"> <\/span>--name<span class=\"o\">=<\/span>mytest\n<\/pre><\/div>\n<p>Result was:<\/p>\n<div class=\"highlight\"><pre><span><\/span>fio<span class=\"w\"> <\/span>--rw<span class=\"o\">=<\/span>write<span class=\"w\"> <\/span>--ioengine<span class=\"o\">=<\/span>sync<span class=\"w\"> <\/span>--fdatasync<span class=\"o\">=<\/span><span class=\"m\">1<\/span><span class=\"w\"> <\/span>--directory<span class=\"o\">=<\/span>test-data<span class=\"w\"> <\/span>--size<span class=\"o\">=<\/span>100m<span class=\"w\"> <\/span>--bs<span class=\"o\">=<\/span><span class=\"m\">2300<\/span><span class=\"w\"> <\/span>--name<span class=\"o\">=<\/span>mytest\nmytest:<span class=\"w\"> <\/span><span class=\"o\">(<\/span><span class=\"nv\">g<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span><span class=\"o\">)<\/span>:<span class=\"w\"> <\/span><span class=\"nv\">rw<\/span><span class=\"o\">=<\/span>write,<span class=\"w\"> <\/span><span class=\"nv\">bs<\/span><span class=\"o\">=(<\/span>R<span class=\"o\">)<\/span><span class=\"w\"> <\/span>2300B-2300B,<span class=\"w\"> <\/span><span class=\"o\">(<\/span>W<span class=\"o\">)<\/span><span class=\"w\"> <\/span>2300B-2300B,<span class=\"w\"> <\/span><span class=\"o\">(<\/span>T<span class=\"o\">)<\/span><span class=\"w\"> <\/span>2300B-2300B,<span class=\"w\"> <\/span><span class=\"nv\">ioengine<\/span><span class=\"o\">=<\/span>sync,<span class=\"w\"> <\/span><span class=\"nv\">iodepth<\/span><span class=\"o\">=<\/span><span class=\"m\">1<\/span>\nfio-3.35-115-g6795\nStarting<span class=\"w\"> <\/span><span class=\"m\">1<\/span><span class=\"w\"> <\/span>process\nJobs:<span class=\"w\"> <\/span><span class=\"m\">1<\/span><span class=\"w\"> <\/span><span class=\"o\">(<\/span><span class=\"nv\">f<\/span><span class=\"o\">=<\/span><span class=\"m\">1<\/span><span class=\"o\">)<\/span>:<span class=\"w\"> <\/span><span class=\"o\">[<\/span>W<span class=\"o\">(<\/span><span class=\"m\">1<\/span><span class=\"o\">)][<\/span><span class=\"m\">98<\/span>.7%<span class=\"o\">][<\/span><span class=\"nv\">w<\/span><span class=\"o\">=<\/span>1967KiB\/s<span class=\"o\">][<\/span><span class=\"nv\">w<\/span><span class=\"o\">=<\/span><span class=\"m\">876<\/span><span class=\"w\"> <\/span>IOPS<span class=\"o\">][<\/span>eta<span class=\"w\"> <\/span>00m:01s<span class=\"o\">]<\/span>\nmytest:<span class=\"w\"> <\/span><span class=\"o\">(<\/span><span class=\"nv\">groupid<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>,<span class=\"w\"> <\/span><span class=\"nv\">jobs<\/span><span class=\"o\">=<\/span><span class=\"m\">1<\/span><span class=\"o\">)<\/span>:<span class=\"w\"> <\/span><span class=\"nv\">err<\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"m\">0<\/span>:<span class=\"w\"> <\/span><span class=\"nv\">pid<\/span><span class=\"o\">=<\/span><span class=\"m\">160845<\/span>:<span class=\"w\"> <\/span>Wed<span class=\"w\"> <\/span>Aug<span class=\"w\"> <\/span><span class=\"m\">16<\/span><span class=\"w\"> <\/span><span class=\"m\">05<\/span>:56:49<span class=\"w\"> <\/span><span class=\"m\">2023<\/span>\n<span class=\"w\">  <\/span>write:<span class=\"w\"> <\/span><span class=\"nv\">IOPS<\/span><span class=\"o\">=<\/span><span class=\"m\">618<\/span>,<span class=\"w\"> <\/span><span class=\"nv\">BW<\/span><span class=\"o\">=<\/span>1388KiB\/s<span class=\"w\"> <\/span><span class=\"o\">(<\/span>1421kB\/s<span class=\"o\">)(<\/span><span class=\"m\">100<\/span>.0MiB\/73768msec<span class=\"o\">)<\/span><span class=\"p\">;<\/span><span class=\"w\"> <\/span><span class=\"m\">0<\/span><span class=\"w\"> <\/span>zone<span class=\"w\"> <\/span>resets\n<span class=\"w\">    <\/span>clat<span class=\"w\"> <\/span><span class=\"o\">(<\/span>usec<span class=\"o\">)<\/span>:<span class=\"w\"> <\/span><span class=\"nv\">min<\/span><span class=\"o\">=<\/span><span class=\"m\">2<\/span>,<span class=\"w\"> <\/span><span class=\"nv\">max<\/span><span class=\"o\">=<\/span><span class=\"m\">20824<\/span>,<span class=\"w\"> <\/span><span class=\"nv\">avg<\/span><span class=\"o\">=<\/span><span class=\"m\">49<\/span>.73,<span class=\"w\"> <\/span><span class=\"nv\">stdev<\/span><span class=\"o\">=<\/span><span class=\"m\">335<\/span>.80\n<span class=\"w\">     <\/span>lat<span class=\"w\"> <\/span><span class=\"o\">(<\/span>usec<span class=\"o\">)<\/span>:<span class=\"w\"> <\/span><span class=\"nv\">min<\/span><span class=\"o\">=<\/span><span class=\"m\">2<\/span>,<span class=\"w\"> <\/span><span class=\"nv\">max<\/span><span class=\"o\">=<\/span><span class=\"m\">20824<\/span>,<span class=\"w\"> <\/span><span class=\"nv\">avg<\/span><span class=\"o\">=<\/span><span class=\"m\">50<\/span>.21,<span class=\"w\"> <\/span><span class=\"nv\">stdev<\/span><span class=\"o\">=<\/span><span class=\"m\">335<\/span>.81\n<span class=\"w\">    <\/span>clat<span class=\"w\"> <\/span>percentiles<span class=\"w\"> <\/span><span class=\"o\">(<\/span>usec<span class=\"o\">)<\/span>:\n<span class=\"w\">     <\/span><span class=\"p\">|<\/span><span class=\"w\">  <\/span><span class=\"m\">1<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">    <\/span><span class=\"m\">6<\/span><span class=\"o\">]<\/span>,<span class=\"w\">  <\/span><span class=\"m\">5<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">    <\/span><span class=\"m\">8<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">10<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">    <\/span><span class=\"m\">9<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">20<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">   <\/span><span class=\"m\">10<\/span><span class=\"o\">]<\/span>,\n<span class=\"w\">     <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span><span class=\"m\">30<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">   <\/span><span class=\"m\">11<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">40<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">   <\/span><span class=\"m\">13<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">50<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">   <\/span><span class=\"m\">14<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">60<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">   <\/span><span class=\"m\">16<\/span><span class=\"o\">]<\/span>,\n<span class=\"w\">     <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span><span class=\"m\">70<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">   <\/span><span class=\"m\">17<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">80<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">   <\/span><span class=\"m\">20<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">90<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">   <\/span><span class=\"m\">29<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">95<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">  <\/span><span class=\"m\">310<\/span><span class=\"o\">]<\/span>,\n<span class=\"w\">     <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span><span class=\"m\">99<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">  <\/span><span class=\"m\">490<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">99<\/span>.50th<span class=\"o\">=[<\/span><span class=\"w\">  <\/span><span class=\"m\">873<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">99<\/span>.90th<span class=\"o\">=[<\/span><span class=\"w\"> <\/span><span class=\"m\">2802<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">99<\/span>.95th<span class=\"o\">=[<\/span><span class=\"w\"> <\/span><span class=\"m\">4293<\/span><span class=\"o\">]<\/span>,\n<span class=\"w\">     <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span><span class=\"m\">99<\/span>.99th<span class=\"o\">=[<\/span><span class=\"m\">20055<\/span><span class=\"o\">]<\/span>\n<span class=\"w\">   <\/span>bw<span class=\"w\"> <\/span><span class=\"o\">(<\/span><span class=\"w\">  <\/span>KiB\/s<span class=\"o\">)<\/span>:<span class=\"w\"> <\/span><span class=\"nv\">min<\/span><span class=\"o\">=<\/span><span class=\"w\">   <\/span><span class=\"m\">44<\/span>,<span class=\"w\"> <\/span><span class=\"nv\">max<\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"m\">2717<\/span>,<span class=\"w\"> <\/span><span class=\"nv\">per<\/span><span class=\"o\">=<\/span><span class=\"m\">99<\/span>.92%,<span class=\"w\"> <\/span><span class=\"nv\">avg<\/span><span class=\"o\">=<\/span><span class=\"m\">1387<\/span>.57,<span class=\"w\"> <\/span><span class=\"nv\">stdev<\/span><span class=\"o\">=<\/span><span class=\"m\">770<\/span>.12,<span class=\"w\"> <\/span><span class=\"nv\">samples<\/span><span class=\"o\">=<\/span><span class=\"m\">147<\/span>\n<span class=\"w\">   <\/span>iops<span class=\"w\">        <\/span>:<span class=\"w\"> <\/span><span class=\"nv\">min<\/span><span class=\"o\">=<\/span><span class=\"w\">   <\/span><span class=\"m\">20<\/span>,<span class=\"w\"> <\/span><span class=\"nv\">max<\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"m\">1210<\/span>,<span class=\"w\"> <\/span><span class=\"nv\">avg<\/span><span class=\"o\">=<\/span><span class=\"m\">617<\/span>.98,<span class=\"w\"> <\/span><span class=\"nv\">stdev<\/span><span class=\"o\">=<\/span><span class=\"m\">342<\/span>.89,<span class=\"w\"> <\/span><span class=\"nv\">samples<\/span><span class=\"o\">=<\/span><span class=\"m\">147<\/span>\n<span class=\"w\">  <\/span>lat<span class=\"w\"> <\/span><span class=\"o\">(<\/span>usec<span class=\"o\">)<\/span><span class=\"w\">   <\/span>:<span class=\"w\"> <\/span><span class=\"nv\">4<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.13%,<span class=\"w\"> <\/span><span class=\"nv\">10<\/span><span class=\"o\">=<\/span><span class=\"m\">21<\/span>.66%,<span class=\"w\"> <\/span><span class=\"nv\">20<\/span><span class=\"o\">=<\/span><span class=\"m\">59<\/span>.34%,<span class=\"w\"> <\/span><span class=\"nv\">50<\/span><span class=\"o\">=<\/span><span class=\"m\">11<\/span>.23%,<span class=\"w\"> <\/span><span class=\"nv\">100<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.76%\n<span class=\"w\">  <\/span>lat<span class=\"w\"> <\/span><span class=\"o\">(<\/span>usec<span class=\"o\">)<\/span><span class=\"w\">   <\/span>:<span class=\"w\"> <\/span><span class=\"nv\">250<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.40%,<span class=\"w\"> <\/span><span class=\"nv\">500<\/span><span class=\"o\">=<\/span><span class=\"m\">5<\/span>.53%,<span class=\"w\"> <\/span><span class=\"nv\">750<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.38%,<span class=\"w\"> <\/span><span class=\"nv\">1000<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.12%\n<span class=\"w\">  <\/span>lat<span class=\"w\"> <\/span><span class=\"o\">(<\/span>msec<span class=\"o\">)<\/span><span class=\"w\">   <\/span>:<span class=\"w\"> <\/span><span class=\"nv\">2<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.25%,<span class=\"w\"> <\/span><span class=\"nv\">4<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.13%,<span class=\"w\"> <\/span><span class=\"nv\">10<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.03%,<span class=\"w\"> <\/span><span class=\"nv\">20<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.01%,<span class=\"w\"> <\/span><span class=\"nv\">50<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.01%\n<span class=\"w\">  <\/span>fsync\/fdatasync\/sync_file_range:\n<span class=\"w\">    <\/span>sync<span class=\"w\"> <\/span><span class=\"o\">(<\/span>usec<span class=\"o\">)<\/span>:<span class=\"w\"> <\/span><span class=\"nv\">min<\/span><span class=\"o\">=<\/span><span class=\"m\">275<\/span>,<span class=\"w\"> <\/span><span class=\"nv\">max<\/span><span class=\"o\">=<\/span><span class=\"m\">181677<\/span>,<span class=\"w\"> <\/span><span class=\"nv\">avg<\/span><span class=\"o\">=<\/span><span class=\"m\">1564<\/span>.33,<span class=\"w\"> <\/span><span class=\"nv\">stdev<\/span><span class=\"o\">=<\/span><span class=\"m\">4190<\/span>.31\n<span class=\"w\">    <\/span>sync<span class=\"w\"> <\/span>percentiles<span class=\"w\"> <\/span><span class=\"o\">(<\/span>usec<span class=\"o\">)<\/span>:\n<span class=\"w\">     <\/span><span class=\"p\">|<\/span><span class=\"w\">  <\/span><span class=\"m\">1<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">   <\/span><span class=\"m\">367<\/span><span class=\"o\">]<\/span>,<span class=\"w\">  <\/span><span class=\"m\">5<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">   <\/span><span class=\"m\">412<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">10<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">   <\/span><span class=\"m\">441<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">20<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">   <\/span><span class=\"m\">486<\/span><span class=\"o\">]<\/span>,\n<span class=\"w\">     <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span><span class=\"m\">30<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">   <\/span><span class=\"m\">537<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">40<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">   <\/span><span class=\"m\">676<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">50<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">   <\/span><span class=\"m\">938<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">60<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">  <\/span><span class=\"m\">1074<\/span><span class=\"o\">]<\/span>,\n<span class=\"w\">     <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span><span class=\"m\">70<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">  <\/span><span class=\"m\">1254<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">80<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">  <\/span><span class=\"m\">1549<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">90<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">  <\/span><span class=\"m\">2343<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">95<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">  <\/span><span class=\"m\">3458<\/span><span class=\"o\">]<\/span>,\n<span class=\"w\">     <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span><span class=\"m\">99<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\"> <\/span><span class=\"m\">19792<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">99<\/span>.50th<span class=\"o\">=[<\/span><span class=\"w\"> <\/span><span class=\"m\">27132<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">99<\/span>.90th<span class=\"o\">=[<\/span><span class=\"w\"> <\/span><span class=\"m\">55837<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">99<\/span>.95th<span class=\"o\">=[<\/span><span class=\"w\"> <\/span><span class=\"m\">76022<\/span><span class=\"o\">]<\/span>,<span class=\"w\">  <\/span><span class=\"c1\">### &lt;&lt;&lt;=== here is 99.00th<\/span>\n<span class=\"w\">     <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span><span class=\"m\">99<\/span>.99th<span class=\"o\">=[<\/span><span class=\"m\">128451<\/span><span class=\"o\">]<\/span>\n<span class=\"w\">  <\/span>cpu<span class=\"w\">          <\/span>:<span class=\"w\"> <\/span><span class=\"nv\">usr<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.49%,<span class=\"w\"> <\/span><span class=\"nv\">sys<\/span><span class=\"o\">=<\/span><span class=\"m\">3<\/span>.04%,<span class=\"w\"> <\/span><span class=\"nv\">ctx<\/span><span class=\"o\">=<\/span><span class=\"m\">165143<\/span>,<span class=\"w\"> <\/span><span class=\"nv\">majf<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>,<span class=\"w\"> <\/span><span class=\"nv\">minf<\/span><span class=\"o\">=<\/span><span class=\"m\">14<\/span>\n<span class=\"w\">  <\/span>IO<span class=\"w\"> <\/span>depths<span class=\"w\">    <\/span>:<span class=\"w\"> <\/span><span class=\"nv\">1<\/span><span class=\"o\">=<\/span><span class=\"m\">200<\/span>.0%,<span class=\"w\"> <\/span><span class=\"nv\">2<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.0%,<span class=\"w\"> <\/span><span class=\"nv\">4<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.0%,<span class=\"w\"> <\/span><span class=\"nv\">8<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.0%,<span class=\"w\"> <\/span><span class=\"nv\">16<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.0%,<span class=\"w\"> <\/span><span class=\"nv\">32<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.0%,<span class=\"w\"> <\/span>&gt;<span class=\"o\">=<\/span><span class=\"nv\">64<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.0%\n<span class=\"w\">     <\/span>submit<span class=\"w\">    <\/span>:<span class=\"w\"> <\/span><span class=\"nv\">0<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.0%,<span class=\"w\"> <\/span><span class=\"nv\">4<\/span><span class=\"o\">=<\/span><span class=\"m\">100<\/span>.0%,<span class=\"w\"> <\/span><span class=\"nv\">8<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.0%,<span class=\"w\"> <\/span><span class=\"nv\">16<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.0%,<span class=\"w\"> <\/span><span class=\"nv\">32<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.0%,<span class=\"w\"> <\/span><span class=\"nv\">64<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.0%,<span class=\"w\"> <\/span>&gt;<span class=\"o\">=<\/span><span class=\"nv\">64<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.0%\n<span class=\"w\">     <\/span><span class=\"nb\">complete<\/span><span class=\"w\">  <\/span>:<span class=\"w\"> <\/span><span class=\"nv\">0<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.0%,<span class=\"w\"> <\/span><span class=\"nv\">4<\/span><span class=\"o\">=<\/span><span class=\"m\">100<\/span>.0%,<span class=\"w\"> <\/span><span class=\"nv\">8<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.0%,<span class=\"w\"> <\/span><span class=\"nv\">16<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.0%,<span class=\"w\"> <\/span><span class=\"nv\">32<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.0%,<span class=\"w\"> <\/span><span class=\"nv\">64<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.0%,<span class=\"w\"> <\/span>&gt;<span class=\"o\">=<\/span><span class=\"nv\">64<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.0%\n<span class=\"w\">     <\/span>issued<span class=\"w\"> <\/span>rwts:<span class=\"w\"> <\/span><span class=\"nv\">total<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>,45590,0,0<span class=\"w\"> <\/span><span class=\"nv\">short<\/span><span class=\"o\">=<\/span><span class=\"m\">45590<\/span>,0,0,0<span class=\"w\"> <\/span><span class=\"nv\">dropped<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>,0,0,0\n<span class=\"w\">     <\/span>latency<span class=\"w\">   <\/span>:<span class=\"w\"> <\/span><span class=\"nv\">target<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>,<span class=\"w\"> <\/span><span class=\"nv\">window<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>,<span class=\"w\"> <\/span><span class=\"nv\">percentile<\/span><span class=\"o\">=<\/span><span class=\"m\">100<\/span>.00%,<span class=\"w\"> <\/span><span class=\"nv\">depth<\/span><span class=\"o\">=<\/span>1Run<span class=\"w\"> <\/span>status<span class=\"w\"> <\/span>group<span class=\"w\"> <\/span><span class=\"m\">0<\/span><span class=\"w\"> <\/span><span class=\"o\">(<\/span>all<span class=\"w\"> <\/span><span class=\"nb\">jobs<\/span><span class=\"o\">)<\/span>:\n<span class=\"w\">  <\/span>WRITE:<span class=\"w\"> <\/span><span class=\"nv\">bw<\/span><span class=\"o\">=<\/span>1388KiB\/s<span class=\"w\"> <\/span><span class=\"o\">(<\/span>1421kB\/s<span class=\"o\">)<\/span>,<span class=\"w\"> <\/span>1388KiB\/s-1388KiB\/s<span class=\"w\"> <\/span><span class=\"o\">(<\/span>1421kB\/s-1421kB\/s<span class=\"o\">)<\/span>,<span class=\"w\"> <\/span><span class=\"nv\">io<\/span><span class=\"o\">=<\/span><span class=\"m\">100<\/span>.0MiB<span class=\"w\"> <\/span><span class=\"o\">(<\/span>105MB<span class=\"o\">)<\/span>,<span class=\"w\"> <\/span><span class=\"nv\">run<\/span><span class=\"o\">=<\/span><span class=\"m\">73768<\/span>-73768msec\nDisk<span class=\"w\"> <\/span>stats<span class=\"w\"> <\/span><span class=\"o\">(<\/span>read\/write<span class=\"o\">)<\/span>:\n<span class=\"w\">  <\/span>vda:<span class=\"w\"> <\/span><span class=\"nv\">ios<\/span><span class=\"o\">=<\/span><span class=\"m\">4601<\/span>\/115020,<span class=\"w\"> <\/span><span class=\"nv\">sectors<\/span><span class=\"o\">=<\/span><span class=\"m\">73144<\/span>\/639377,<span class=\"w\"> <\/span><span class=\"nv\">merge<\/span><span class=\"o\">=<\/span><span class=\"m\">1<\/span>\/796,<span class=\"w\"> <\/span><span class=\"nv\">ticks<\/span><span class=\"o\">=<\/span><span class=\"m\">5288<\/span>\/85834,<span class=\"w\"> <\/span><span class=\"nv\">in_queue<\/span><span class=\"o\">=<\/span><span class=\"m\">122603<\/span>,<span class=\"w\"> <\/span><span class=\"nv\">util<\/span><span class=\"o\">=<\/span><span class=\"m\">97<\/span>.44%\n<\/pre><\/div>\n<p>To explain those results in a few words: <cite>In 99, it has 19792, so it means 19.79 ms<\/cite>,\nand it is recommended to have below 10 ms <a class=\"reference external\" href=\"https:\/\/etcd.io\/docs\/v3.5\/op-guide\/hardware\/#disks\">source<\/a>.<\/p>\n<p>The result was very similar for different hypervisors, but still, we cannot assume,\nthat it is a disk issue, but these results support this theory.<\/p>\n<p>Just to compare results for fio, where storage is on <em>RAM disk<\/em>:<\/p>\n<div class=\"highlight\"><pre><span><\/span>fio<span class=\"w\"> <\/span>--rw<span class=\"o\">=<\/span>write<span class=\"w\"> <\/span>--ioengine<span class=\"o\">=<\/span>sync<span class=\"w\"> <\/span>--fdatasync<span class=\"o\">=<\/span><span class=\"m\">1<\/span><span class=\"w\"> <\/span>--directory<span class=\"o\">=<\/span>\/home\/zuul-worker\/etcd\/data\/fio<span class=\"w\"> <\/span>--size<span class=\"o\">=<\/span>100m<span class=\"w\"> <\/span>--bs<span class=\"o\">=<\/span><span class=\"m\">2300<\/span><span class=\"w\"> <\/span>--name<span class=\"o\">=<\/span>mytest\nmytest:<span class=\"w\"> <\/span><span class=\"o\">(<\/span><span class=\"nv\">g<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span><span class=\"o\">)<\/span>:<span class=\"w\"> <\/span><span class=\"nv\">rw<\/span><span class=\"o\">=<\/span>write,<span class=\"w\"> <\/span><span class=\"nv\">bs<\/span><span class=\"o\">=(<\/span>R<span class=\"o\">)<\/span><span class=\"w\"> <\/span>2300B-2300B,<span class=\"w\"> <\/span><span class=\"o\">(<\/span>W<span class=\"o\">)<\/span><span class=\"w\"> <\/span>2300B-2300B,<span class=\"w\"> <\/span><span class=\"o\">(<\/span>T<span class=\"o\">)<\/span><span class=\"w\"> <\/span>2300B-2300B,<span class=\"w\"> <\/span><span class=\"nv\">ioengine<\/span><span class=\"o\">=<\/span>sync,<span class=\"w\"> <\/span><span class=\"nv\">iodepth<\/span><span class=\"o\">=<\/span><span class=\"m\">1<\/span>\nfio-3.35-138-g50b94\nStarting<span class=\"w\"> <\/span><span class=\"m\">1<\/span><span class=\"w\"> <\/span>process\nmytest:<span class=\"w\"> <\/span>Laying<span class=\"w\"> <\/span>out<span class=\"w\"> <\/span>IO<span class=\"w\"> <\/span>file<span class=\"w\"> <\/span><span class=\"o\">(<\/span><span class=\"m\">1<\/span><span class=\"w\"> <\/span>file<span class=\"w\"> <\/span>\/<span class=\"w\"> <\/span>100MiB<span class=\"o\">)<\/span>\n\nmytest:<span class=\"w\"> <\/span><span class=\"o\">(<\/span><span class=\"nv\">groupid<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>,<span class=\"w\"> <\/span><span class=\"nv\">jobs<\/span><span class=\"o\">=<\/span><span class=\"m\">1<\/span><span class=\"o\">)<\/span>:<span class=\"w\"> <\/span><span class=\"nv\">err<\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"m\">0<\/span>:<span class=\"w\"> <\/span><span class=\"nv\">pid<\/span><span class=\"o\">=<\/span><span class=\"m\">10092<\/span>:<span class=\"w\"> <\/span>Mon<span class=\"w\"> <\/span>Oct<span class=\"w\"> <\/span><span class=\"m\">16<\/span><span class=\"w\"> <\/span><span class=\"m\">10<\/span>:06:06<span class=\"w\"> <\/span><span class=\"m\">2023<\/span>\n<span class=\"w\">  <\/span>write:<span class=\"w\"> <\/span><span class=\"nv\">IOPS<\/span><span class=\"o\">=<\/span>451k,<span class=\"w\"> <\/span><span class=\"nv\">BW<\/span><span class=\"o\">=<\/span>990MiB\/s<span class=\"w\"> <\/span><span class=\"o\">(<\/span>1038MB\/s<span class=\"o\">)(<\/span><span class=\"m\">100<\/span>.0MiB\/101msec<span class=\"o\">)<\/span><span class=\"p\">;<\/span><span class=\"w\"> <\/span><span class=\"m\">0<\/span><span class=\"w\"> <\/span>zone<span class=\"w\"> <\/span>resets\n<span class=\"w\">    <\/span>clat<span class=\"w\"> <\/span><span class=\"o\">(<\/span>nsec<span class=\"o\">)<\/span>:<span class=\"w\"> <\/span><span class=\"nv\">min<\/span><span class=\"o\">=<\/span><span class=\"m\">621<\/span>,<span class=\"w\"> <\/span><span class=\"nv\">max<\/span><span class=\"o\">=<\/span><span class=\"m\">568765<\/span>,<span class=\"w\"> <\/span><span class=\"nv\">avg<\/span><span class=\"o\">=<\/span><span class=\"m\">1370<\/span>.13,<span class=\"w\"> <\/span><span class=\"nv\">stdev<\/span><span class=\"o\">=<\/span><span class=\"m\">6496<\/span>.39\n<span class=\"w\">     <\/span>lat<span class=\"w\"> <\/span><span class=\"o\">(<\/span>nsec<span class=\"o\">)<\/span>:<span class=\"w\"> <\/span><span class=\"nv\">min<\/span><span class=\"o\">=<\/span><span class=\"m\">670<\/span>,<span class=\"w\"> <\/span><span class=\"nv\">max<\/span><span class=\"o\">=<\/span><span class=\"m\">568835<\/span>,<span class=\"w\"> <\/span><span class=\"nv\">avg<\/span><span class=\"o\">=<\/span><span class=\"m\">1430<\/span>.42,<span class=\"w\"> <\/span><span class=\"nv\">stdev<\/span><span class=\"o\">=<\/span><span class=\"m\">6498<\/span>.82\n<span class=\"w\">    <\/span>clat<span class=\"w\"> <\/span>percentiles<span class=\"w\"> <\/span><span class=\"o\">(<\/span>nsec<span class=\"o\">)<\/span>:\n<span class=\"w\">     <\/span><span class=\"p\">|<\/span><span class=\"w\">  <\/span><span class=\"m\">1<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">   <\/span><span class=\"m\">668<\/span><span class=\"o\">]<\/span>,<span class=\"w\">  <\/span><span class=\"m\">5<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">   <\/span><span class=\"m\">668<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">10<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">   <\/span><span class=\"m\">684<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">20<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">   <\/span><span class=\"m\">692<\/span><span class=\"o\">]<\/span>,\n<span class=\"w\">     <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span><span class=\"m\">30<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">   <\/span><span class=\"m\">924<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">40<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">  <\/span><span class=\"m\">1128<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">50<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">  <\/span><span class=\"m\">1176<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">60<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">  <\/span><span class=\"m\">1208<\/span><span class=\"o\">]<\/span>,\n<span class=\"w\">     <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span><span class=\"m\">70<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">  <\/span><span class=\"m\">1288<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">80<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">  <\/span><span class=\"m\">1544<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">90<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">  <\/span><span class=\"m\">2024<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">95<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">  <\/span><span class=\"m\">2320<\/span><span class=\"o\">]<\/span>,\n<span class=\"w\">     <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span><span class=\"m\">99<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">  <\/span><span class=\"m\">3312<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">99<\/span>.50th<span class=\"o\">=[<\/span><span class=\"w\">  <\/span><span class=\"m\">4192<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">99<\/span>.90th<span class=\"o\">=[<\/span><span class=\"w\"> <\/span><span class=\"m\">14528<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">99<\/span>.95th<span class=\"o\">=[<\/span><span class=\"w\"> <\/span><span class=\"m\">35072<\/span><span class=\"o\">]<\/span>,\n<span class=\"w\">     <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span><span class=\"m\">99<\/span>.99th<span class=\"o\">=[<\/span><span class=\"m\">452608<\/span><span class=\"o\">]<\/span>\n<span class=\"w\">  <\/span>lat<span class=\"w\"> <\/span><span class=\"o\">(<\/span>nsec<span class=\"o\">)<\/span><span class=\"w\">   <\/span>:<span class=\"w\"> <\/span><span class=\"nv\">750<\/span><span class=\"o\">=<\/span><span class=\"m\">28<\/span>.18%,<span class=\"w\"> <\/span><span class=\"nv\">1000<\/span><span class=\"o\">=<\/span><span class=\"m\">6<\/span>.13%\n<span class=\"w\">  <\/span>lat<span class=\"w\"> <\/span><span class=\"o\">(<\/span>usec<span class=\"o\">)<\/span><span class=\"w\">   <\/span>:<span class=\"w\"> <\/span><span class=\"nv\">2<\/span><span class=\"o\">=<\/span><span class=\"m\">54<\/span>.33%,<span class=\"w\"> <\/span><span class=\"nv\">4<\/span><span class=\"o\">=<\/span><span class=\"m\">10<\/span>.81%,<span class=\"w\"> <\/span><span class=\"nv\">10<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.34%,<span class=\"w\"> <\/span><span class=\"nv\">20<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.14%,<span class=\"w\"> <\/span><span class=\"nv\">50<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.05%\n<span class=\"w\">  <\/span>lat<span class=\"w\"> <\/span><span class=\"o\">(<\/span>usec<span class=\"o\">)<\/span><span class=\"w\">   <\/span>:<span class=\"w\"> <\/span><span class=\"nv\">100<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.01%,<span class=\"w\"> <\/span><span class=\"nv\">250<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.01%,<span class=\"w\"> <\/span><span class=\"nv\">500<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.02%,<span class=\"w\"> <\/span><span class=\"nv\">750<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.01%\n<span class=\"w\">  <\/span>fsync\/fdatasync\/sync_file_range:\n<span class=\"w\">    <\/span>sync<span class=\"w\"> <\/span><span class=\"o\">(<\/span>nsec<span class=\"o\">)<\/span>:<span class=\"w\"> <\/span><span class=\"nv\">min<\/span><span class=\"o\">=<\/span><span class=\"m\">200<\/span>,<span class=\"w\"> <\/span><span class=\"nv\">max<\/span><span class=\"o\">=<\/span><span class=\"m\">109123<\/span>,<span class=\"w\"> <\/span><span class=\"nv\">avg<\/span><span class=\"o\">=<\/span><span class=\"m\">259<\/span>.84,<span class=\"w\"> <\/span><span class=\"nv\">stdev<\/span><span class=\"o\">=<\/span><span class=\"m\">608<\/span>.43\n<span class=\"w\">    <\/span>sync<span class=\"w\"> <\/span>percentiles<span class=\"w\"> <\/span><span class=\"o\">(<\/span>nsec<span class=\"o\">)<\/span>:\n<span class=\"w\">     <\/span><span class=\"p\">|<\/span><span class=\"w\">  <\/span><span class=\"m\">1<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">  <\/span><span class=\"m\">211<\/span><span class=\"o\">]<\/span>,<span class=\"w\">  <\/span><span class=\"m\">5<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">  <\/span><span class=\"m\">221<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">10<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">  <\/span><span class=\"m\">221<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">20<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">  <\/span><span class=\"m\">221<\/span><span class=\"o\">]<\/span>,\n<span class=\"w\">     <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span><span class=\"m\">30<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">  <\/span><span class=\"m\">221<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">40<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">  <\/span><span class=\"m\">221<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">50<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">  <\/span><span class=\"m\">231<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">60<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">  <\/span><span class=\"m\">231<\/span><span class=\"o\">]<\/span>,\n<span class=\"w\">     <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span><span class=\"m\">70<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">  <\/span><span class=\"m\">241<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">80<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">  <\/span><span class=\"m\">302<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">90<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">  <\/span><span class=\"m\">330<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">95<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">  <\/span><span class=\"m\">350<\/span><span class=\"o\">]<\/span>,\n<span class=\"w\">     <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span><span class=\"m\">99<\/span>.00th<span class=\"o\">=[<\/span><span class=\"w\">  <\/span><span class=\"m\">382<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">99<\/span>.50th<span class=\"o\">=[<\/span><span class=\"w\">  <\/span><span class=\"m\">410<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">99<\/span>.90th<span class=\"o\">=[<\/span><span class=\"w\">  <\/span><span class=\"m\">660<\/span><span class=\"o\">]<\/span>,<span class=\"w\"> <\/span><span class=\"m\">99<\/span>.95th<span class=\"o\">=[<\/span><span class=\"w\">  <\/span><span class=\"m\">932<\/span><span class=\"o\">]<\/span>,\n<span class=\"w\">     <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span><span class=\"m\">99<\/span>.99th<span class=\"o\">=[<\/span><span class=\"m\">12608<\/span><span class=\"o\">]<\/span>\n<span class=\"w\">  <\/span>cpu<span class=\"w\">          <\/span>:<span class=\"w\"> <\/span><span class=\"nv\">usr<\/span><span class=\"o\">=<\/span><span class=\"m\">40<\/span>.00%,<span class=\"w\"> <\/span><span class=\"nv\">sys<\/span><span class=\"o\">=<\/span><span class=\"m\">59<\/span>.00%,<span class=\"w\"> <\/span><span class=\"nv\">ctx<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>,<span class=\"w\"> <\/span><span class=\"nv\">majf<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>,<span class=\"w\"> <\/span><span class=\"nv\">minf<\/span><span class=\"o\">=<\/span><span class=\"m\">11<\/span>\n<span class=\"w\">  <\/span>IO<span class=\"w\"> <\/span>depths<span class=\"w\">    <\/span>:<span class=\"w\"> <\/span><span class=\"nv\">1<\/span><span class=\"o\">=<\/span><span class=\"m\">200<\/span>.0%,<span class=\"w\"> <\/span><span class=\"nv\">2<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.0%,<span class=\"w\"> <\/span><span class=\"nv\">4<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.0%,<span class=\"w\"> <\/span><span class=\"nv\">8<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.0%,<span class=\"w\"> <\/span><span class=\"nv\">16<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.0%,<span class=\"w\"> <\/span><span class=\"nv\">32<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.0%,<span class=\"w\"> <\/span>&gt;<span class=\"o\">=<\/span><span class=\"nv\">64<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.0%\n<span class=\"w\">     <\/span>submit<span class=\"w\">    <\/span>:<span class=\"w\"> <\/span><span class=\"nv\">0<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.0%,<span class=\"w\"> <\/span><span class=\"nv\">4<\/span><span class=\"o\">=<\/span><span class=\"m\">100<\/span>.0%,<span class=\"w\"> <\/span><span class=\"nv\">8<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.0%,<span class=\"w\"> <\/span><span class=\"nv\">16<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.0%,<span class=\"w\"> <\/span><span class=\"nv\">32<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.0%,<span class=\"w\"> <\/span><span class=\"nv\">64<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.0%,<span class=\"w\"> <\/span>&gt;<span class=\"o\">=<\/span><span class=\"nv\">64<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.0%\n<span class=\"w\">     <\/span><span class=\"nb\">complete<\/span><span class=\"w\">  <\/span>:<span class=\"w\"> <\/span><span class=\"nv\">0<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.0%,<span class=\"w\"> <\/span><span class=\"nv\">4<\/span><span class=\"o\">=<\/span><span class=\"m\">100<\/span>.0%,<span class=\"w\"> <\/span><span class=\"nv\">8<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.0%,<span class=\"w\"> <\/span><span class=\"nv\">16<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.0%,<span class=\"w\"> <\/span><span class=\"nv\">32<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.0%,<span class=\"w\"> <\/span><span class=\"nv\">64<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.0%,<span class=\"w\"> <\/span>&gt;<span class=\"o\">=<\/span><span class=\"nv\">64<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>.0%\n<span class=\"w\">     <\/span>issued<span class=\"w\"> <\/span>rwts:<span class=\"w\"> <\/span><span class=\"nv\">total<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>,45590,0,0<span class=\"w\"> <\/span><span class=\"nv\">short<\/span><span class=\"o\">=<\/span><span class=\"m\">45590<\/span>,0,0,0<span class=\"w\"> <\/span><span class=\"nv\">dropped<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>,0,0,0\n<span class=\"w\">     <\/span>latency<span class=\"w\">   <\/span>:<span class=\"w\"> <\/span><span class=\"nv\">target<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>,<span class=\"w\"> <\/span><span class=\"nv\">window<\/span><span class=\"o\">=<\/span><span class=\"m\">0<\/span>,<span class=\"w\"> <\/span><span class=\"nv\">percentile<\/span><span class=\"o\">=<\/span><span class=\"m\">100<\/span>.00%,<span class=\"w\"> <\/span><span class=\"nv\">depth<\/span><span class=\"o\">=<\/span><span class=\"m\">1<\/span>\n\nRun<span class=\"w\"> <\/span>status<span class=\"w\"> <\/span>group<span class=\"w\"> <\/span><span class=\"m\">0<\/span><span class=\"w\"> <\/span><span class=\"o\">(<\/span>all<span class=\"w\"> <\/span><span class=\"nb\">jobs<\/span><span class=\"o\">)<\/span>:\n<span class=\"w\">  <\/span>WRITE:<span class=\"w\"> <\/span><span class=\"nv\">bw<\/span><span class=\"o\">=<\/span>990MiB\/s<span class=\"w\"> <\/span><span class=\"o\">(<\/span>1038MB\/s<span class=\"o\">)<\/span>,<span class=\"w\"> <\/span>990MiB\/s-990MiB\/s<span class=\"w\"> <\/span><span class=\"o\">(<\/span>1038MB\/s-1038MB\/s<span class=\"o\">)<\/span>,<span class=\"w\"> <\/span><span class=\"nv\">io<\/span><span class=\"o\">=<\/span><span class=\"m\">100<\/span>.0MiB<span class=\"w\"> <\/span><span class=\"o\">(<\/span>105MB<span class=\"o\">)<\/span>,<span class=\"w\"> <\/span><span class=\"nv\">run<\/span><span class=\"o\">=<\/span><span class=\"m\">101<\/span>-101msec\n<\/pre><\/div>\n<p>To explain that results in few words: <cite>In 99, it has 382 so it means 0.382ms<\/cite>.\nResult of that test was obvious, but in later part of that blog, I will be\ndoing a test of etcd benchmark, where the data directory will be mounted\non the RAM disk.<\/p>\n<\/div>\n<div class=\"section\" id=\"phoronix-test-suite\">\n<h3>Phoronix test suite<\/h3>\n<p>How we test on Centos 9 stream:<\/p>\n<div class=\"highlight\"><pre><span><\/span>sudo<span class=\"w\"> <\/span>dnf<span class=\"w\"> <\/span>install<span class=\"w\"> <\/span>-y<span class=\"w\"> <\/span>php-cli<span class=\"w\"> <\/span>php-xml<span class=\"w\"> <\/span>php-json<span class=\"w\"> <\/span>git\n\ngit<span class=\"w\"> <\/span>clone<span class=\"w\"> <\/span>https:\/\/github.com\/phoronix-test-suite\/phoronix-test-suite<span class=\"w\"> <\/span><span class=\"o\">&amp;&amp;<\/span><span class=\"w\"> <\/span><span class=\"nb\">cd<\/span><span class=\"w\"> <\/span>phoronix-test-suite\/\nsudo<span class=\"w\"> <\/span>.\/install-sh\n\nsudo<span class=\"w\"> <\/span>phoronix-test-suite<span class=\"w\"> <\/span>run<span class=\"w\"> <\/span>pts\/etcd\n<\/pre><\/div>\n<p>We will not go into details here, but the results showed operational values\nthat were much below expected values and didn't match minimal requirements\nfor the etcd service.\nWhole results you can find <a class=\"reference external\" href=\"https:\/\/openbenchmarking.org\/result\/2308286-NE-ALL32952239\">here<\/a>.<\/p>\n<\/div>\n<div class=\"section\" id=\"etcd-benchmark-tool\">\n<h3>Etcd benchmark tool<\/h3>\n<p>The same benchmark is done in the Phoronix test suite, but the below playbook will just\nrun single tests, and it might be helpful for those who don't want to use\nmany scenarios, as the Phoronix test suite does.<\/p>\n<p>To visualize the difference between etcd on RAM disk and on the SSD disk,\nI will run the etcd <a class=\"reference external\" href=\"https:\/\/etcd.io\/docs\/v3.5\/op-guide\/performance\/\">benchmark<\/a> tool,\nby using the simple Ansible playbook:<\/p>\n<ul class=\"simple\">\n<li>benchmark.yaml file<\/li>\n<\/ul>\n<div class=\"highlight\"><pre><span><\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">Benchmark etcd<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">hosts<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">somehost.dev<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">vars<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\"># once it would be true, once false. Depends what test is done<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">etcd_ramdisk<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">true<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">ramdisk_size<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">4096m<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">ramdisk_path<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;~{{<\/span><span class=\"nv\"> <\/span><span class=\"s\">ansible_user<\/span><span class=\"nv\"> <\/span><span class=\"s\">|<\/span><span class=\"nv\"> <\/span><span class=\"s\">default(ansible_user_id)<\/span><span class=\"nv\"> <\/span><span class=\"s\">}}\/etcd\/data&quot;<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">etcd_version<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">3.4.27<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">tasks<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">    <\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">Install required packages<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">become<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">true<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">ansible.builtin.package<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">golang<\/span>\n\n<span class=\"w\">    <\/span><span class=\"c1\">### RAMDISK<\/span>\n<span class=\"w\">    <\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">Configure RAMDISK for etcd<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">when<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">etcd_ramdisk<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">block<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">        <\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">Create directory for etcd<\/span>\n<span class=\"w\">          <\/span><span class=\"nt\">become<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">true<\/span>\n<span class=\"w\">          <\/span><span class=\"nt\">ansible.builtin.file<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">path<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;{{<\/span><span class=\"nv\"> <\/span><span class=\"s\">ramdisk_path<\/span><span class=\"nv\"> <\/span><span class=\"s\">}}&quot;<\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">state<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">directory<\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">mode<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">0700<\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">owner<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;{{<\/span><span class=\"nv\"> <\/span><span class=\"s\">ansible_user<\/span><span class=\"nv\"> <\/span><span class=\"s\">|<\/span><span class=\"nv\"> <\/span><span class=\"s\">default(ansible_user_id)<\/span><span class=\"nv\"> <\/span><span class=\"s\">}}&quot;<\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">group<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;{{<\/span><span class=\"nv\"> <\/span><span class=\"s\">ansible_user<\/span><span class=\"nv\"> <\/span><span class=\"s\">|<\/span><span class=\"nv\"> <\/span><span class=\"s\">default(ansible_user_id)<\/span><span class=\"nv\"> <\/span><span class=\"s\">}}&quot;<\/span>\n\n<span class=\"w\">        <\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">Mount ramdisk<\/span>\n<span class=\"w\">          <\/span><span class=\"nt\">become<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">true<\/span>\n<span class=\"w\">          <\/span><span class=\"nt\">ansible.posix.mount<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">src<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">tmpfs<\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;{{<\/span><span class=\"nv\"> <\/span><span class=\"s\">ramdisk_path<\/span><span class=\"nv\"> <\/span><span class=\"s\">}}&quot;<\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">fstype<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">tmpfs<\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">state<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">mounted<\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">opts<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;defaults,size={{<\/span><span class=\"nv\"> <\/span><span class=\"s\">ramdisk_size<\/span><span class=\"nv\"> <\/span><span class=\"s\">}}&quot;<\/span>\n\n<span class=\"w\">        <\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">Set proper permissions after mount<\/span>\n<span class=\"w\">          <\/span><span class=\"nt\">become<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">true<\/span>\n<span class=\"w\">          <\/span><span class=\"nt\">ansible.builtin.file<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">path<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;{{<\/span><span class=\"nv\"> <\/span><span class=\"s\">ramdisk_path<\/span><span class=\"nv\"> <\/span><span class=\"s\">}}&quot;<\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">state<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">directory<\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">mode<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">0700<\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">owner<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;{{<\/span><span class=\"nv\"> <\/span><span class=\"s\">ansible_user<\/span><span class=\"nv\"> <\/span><span class=\"s\">|<\/span><span class=\"nv\"> <\/span><span class=\"s\">default(ansible_user_id)<\/span><span class=\"nv\"> <\/span><span class=\"s\">}}&quot;<\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">group<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;{{<\/span><span class=\"nv\"> <\/span><span class=\"s\">ansible_user<\/span><span class=\"nv\"> <\/span><span class=\"s\">|<\/span><span class=\"nv\"> <\/span><span class=\"s\">default(ansible_user_id)<\/span><span class=\"nv\"> <\/span><span class=\"s\">}}&quot;<\/span>\n\n<span class=\"w\">        <\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">Set proper SELinux context<\/span>\n<span class=\"w\">          <\/span><span class=\"nt\">become<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">true<\/span>\n<span class=\"w\">          <\/span><span class=\"nt\">ansible.builtin.command<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">restorecon -F {{ ramdisk_path }}<\/span>\n\n<span class=\"w\">    <\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">Create directory for etcd<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">ansible.builtin.file<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">path<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">~\/etcd<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">state<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">directory<\/span>\n\n<span class=\"w\">    <\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">Download etcd<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">ansible.builtin.get_url<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">url<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">https:\/\/github.com\/etcd-io\/etcd\/releases\/download\/v{{ etcd_version }}\/etcd-v{{ etcd_version }}-linux-amd64.tar.gz<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">dest<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">\/tmp\/<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">mode<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;0644&quot;<\/span>\n\n<span class=\"w\">    <\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">Unarchive etcd<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">ansible.builtin.unarchive<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">src<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;\/tmp\/etcd-v{{<\/span><span class=\"nv\"> <\/span><span class=\"s\">etcd_version<\/span><span class=\"nv\"> <\/span><span class=\"s\">}}-linux-amd64.tar.gz&quot;<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">dest<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">~\/etcd<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">remote_src<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">true<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">extra_opts<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">          <\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;--strip-components=1&quot;<\/span>\n\n<span class=\"w\">    <\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">Check if etcd is not already running<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">ansible.builtin.wait_for<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">host<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">127.0.0.1<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">port<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">2379<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">state<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">started<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">delay<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">0<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">timeout<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">5<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">ignore_errors<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">true<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">register<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">_etcd_running<\/span>\n\n<span class=\"w\">    <\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">Start etcd as subprocess<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">when<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;&#39;failed&#39;<\/span><span class=\"nv\"> <\/span><span class=\"s\">in<\/span><span class=\"nv\"> <\/span><span class=\"s\">_etcd_running<\/span><span class=\"nv\"> <\/span><span class=\"s\">and<\/span><span class=\"nv\"> <\/span><span class=\"s\">_etcd_running.failed&quot;<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">ansible.builtin.shell<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p p-Indicator\">&gt;<\/span>\n<span class=\"w\">        <\/span><span class=\"no\">~\/etcd\/etcd<\/span>\n<span class=\"w\">        <\/span><span class=\"no\">--snapshot-count=5000<\/span>\n<span class=\"w\">        <\/span><span class=\"no\">--auto-compaction-retention=10<\/span>\n<span class=\"w\">        <\/span><span class=\"no\">--auto-compaction-mode=revision<\/span>\n<span class=\"w\">        <\/span><span class=\"no\">--data-dir {{ ramdisk_path }}<\/span>\n<span class=\"w\">        <\/span><span class=\"no\">&amp;&gt; ~\/etcd.log<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">async<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">7200<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">poll<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">0<\/span>\n\n<span class=\"w\">    <\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">Clone etcd repo<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">ansible.builtin.git<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">repo<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">https:\/\/github.com\/etcd-io\/etcd<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">dest<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">~\/etcd-repo<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">version<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;v{{<\/span><span class=\"nv\"> <\/span><span class=\"s\">etcd_version<\/span><span class=\"nv\"> <\/span><span class=\"s\">}}&quot;<\/span>\n\n<span class=\"w\">    <\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">Install benchmark<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">ansible.builtin.shell<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p p-Indicator\">|<\/span>\n<span class=\"w\">        <\/span><span class=\"no\">go install -v .\/tools\/benchmark<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">args<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">chdir<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">~\/etcd-repo<\/span>\n\n<span class=\"w\">    <\/span><span class=\"c1\"># https:\/\/github.com\/phoronix-test-suite\/phoronix-test-suite\/blob\/master\/ob-cache\/test-profiles\/pts\/etcd-1.0.0\/test-definition.xml<\/span>\n<span class=\"w\">    <\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">Run benchmark<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">ansible.builtin.shell<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p p-Indicator\">&gt;<\/span>\n<span class=\"w\">        <\/span><span class=\"no\">~\/go\/bin\/benchmark<\/span>\n<span class=\"w\">        <\/span><span class=\"no\">--endpoints=127.0.0.1:2379<\/span>\n<span class=\"w\">        <\/span><span class=\"no\">--target-leader<\/span>\n<span class=\"w\">        <\/span><span class=\"no\">--conns=100<\/span>\n<span class=\"w\">        <\/span><span class=\"no\">--clients=100<\/span>\n<span class=\"w\">        <\/span><span class=\"no\">put<\/span>\n<span class=\"w\">        <\/span><span class=\"no\">--key-size=8<\/span>\n<span class=\"w\">        <\/span><span class=\"no\">--sequential-keys<\/span>\n<span class=\"w\">        <\/span><span class=\"no\">--total=4000000<\/span>\n<span class=\"w\">        <\/span><span class=\"no\">--val-size=256<\/span>\n<span class=\"w\">        <\/span><span class=\"no\">&amp;&gt; ~\/benchmark.log<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">args<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">chdir<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">~\/etcd-repo<\/span>\n<\/pre><\/div>\n<ul class=\"simple\">\n<li>inventory file<\/li>\n<\/ul>\n<div class=\"highlight\"><pre><span><\/span>cat<span class=\"w\"> <\/span>&lt;&lt;<span class=\"w\"> <\/span>EOF<span class=\"w\"> <\/span>&gt;<span class=\"w\"> <\/span>inventory.yaml\n---\nall:\n<span class=\"w\">  <\/span>hosts:\n<span class=\"w\">    <\/span>somehost.dev\n<span class=\"w\">      <\/span>ansible_port:<span class=\"w\"> <\/span><span class=\"m\">22<\/span>\n<span class=\"w\">      <\/span>ansible_host:<span class=\"w\"> <\/span>myipaddress\n<span class=\"w\">      <\/span>ansible_user:<span class=\"w\"> <\/span>centos\n<\/pre><\/div>\n<p>and then Ansible execution looks like:<\/p>\n<div class=\"highlight\"><pre><span><\/span>ansible-playbook<span class=\"w\"> <\/span>-i<span class=\"w\"> <\/span>inventory.yaml<span class=\"w\"> <\/span>benchmark.yaml\n<\/pre><\/div>\n<div class=\"section\" id=\"results-on-ramdisk\">\n<h4>Results on ramdisk<\/h4>\n<div class=\"highlight\"><pre><span><\/span><span class=\"m\">4000000<\/span><span class=\"w\"> <\/span>\/<span class=\"w\"> <\/span><span class=\"m\">4000000<\/span><span class=\"w\">  <\/span><span class=\"m\">100<\/span>.00%<span class=\"w\"> <\/span>2m14ss\n\nSummary:\n<span class=\"w\">  <\/span>Total:<span class=\"w\">        <\/span><span class=\"m\">134<\/span>.9707<span class=\"w\"> <\/span>secs.\n<span class=\"w\">  <\/span>Slowest:<span class=\"w\">      <\/span><span class=\"m\">0<\/span>.0322<span class=\"w\"> <\/span>secs.\n<span class=\"w\">  <\/span>Fastest:<span class=\"w\">      <\/span><span class=\"m\">0<\/span>.0002<span class=\"w\"> <\/span>secs.\n<span class=\"w\">  <\/span>Average:<span class=\"w\">      <\/span><span class=\"m\">0<\/span>.0032<span class=\"w\"> <\/span>secs.\n<span class=\"w\">  <\/span>Stddev:<span class=\"w\">       <\/span><span class=\"m\">0<\/span>.0015<span class=\"w\"> <\/span>secs.\n<span class=\"w\">  <\/span>Requests\/sec:<span class=\"w\"> <\/span><span class=\"m\">29636<\/span>.0538\n\nResponse<span class=\"w\"> <\/span><span class=\"nb\">time<\/span><span class=\"w\"> <\/span>histogram:\n<span class=\"w\">  <\/span><span class=\"m\">0<\/span>.0002<span class=\"w\"> <\/span><span class=\"o\">[<\/span><span class=\"m\">1<\/span><span class=\"o\">]<\/span><span class=\"w\">    <\/span><span class=\"p\">|<\/span>\n<span class=\"w\">  <\/span><span class=\"m\">0<\/span>.0034<span class=\"w\"> <\/span><span class=\"o\">[<\/span><span class=\"m\">2465154<\/span><span class=\"o\">]<\/span><span class=\"w\">      <\/span><span class=\"p\">|<\/span>\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\n<span class=\"w\">  <\/span><span class=\"m\">0<\/span>.0066<span class=\"w\"> <\/span><span class=\"o\">[<\/span><span class=\"m\">1405963<\/span><span class=\"o\">]<\/span><span class=\"w\">      <\/span><span class=\"p\">|<\/span>\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\n<span class=\"w\">  <\/span><span class=\"m\">0<\/span>.0098<span class=\"w\"> <\/span><span class=\"o\">[<\/span><span class=\"m\">109453<\/span><span class=\"o\">]<\/span><span class=\"w\">       <\/span><span class=\"p\">|<\/span>\u220e\n<span class=\"w\">  <\/span><span class=\"m\">0<\/span>.0130<span class=\"w\"> <\/span><span class=\"o\">[<\/span><span class=\"m\">16145<\/span><span class=\"o\">]<\/span><span class=\"w\">        <\/span><span class=\"p\">|<\/span>\n<span class=\"w\">  <\/span><span class=\"m\">0<\/span>.0162<span class=\"w\"> <\/span><span class=\"o\">[<\/span><span class=\"m\">2288<\/span><span class=\"o\">]<\/span><span class=\"w\"> <\/span><span class=\"p\">|<\/span>\n<span class=\"w\">  <\/span><span class=\"m\">0<\/span>.0194<span class=\"w\"> <\/span><span class=\"o\">[<\/span><span class=\"m\">535<\/span><span class=\"o\">]<\/span><span class=\"w\">  <\/span><span class=\"p\">|<\/span>\n<span class=\"w\">  <\/span><span class=\"m\">0<\/span>.0226<span class=\"w\"> <\/span><span class=\"o\">[<\/span><span class=\"m\">279<\/span><span class=\"o\">]<\/span><span class=\"w\">  <\/span><span class=\"p\">|<\/span>\n<span class=\"w\">  <\/span><span class=\"m\">0<\/span>.0258<span class=\"w\"> <\/span><span class=\"o\">[<\/span><span class=\"m\">145<\/span><span class=\"o\">]<\/span><span class=\"w\">  <\/span><span class=\"p\">|<\/span>\n<span class=\"w\">  <\/span><span class=\"m\">0<\/span>.0290<span class=\"w\"> <\/span><span class=\"o\">[<\/span><span class=\"m\">31<\/span><span class=\"o\">]<\/span><span class=\"w\">   <\/span><span class=\"p\">|<\/span>\n<span class=\"w\">  <\/span><span class=\"m\">0<\/span>.0322<span class=\"w\"> <\/span><span class=\"o\">[<\/span><span class=\"m\">6<\/span><span class=\"o\">]<\/span><span class=\"w\">    <\/span><span class=\"p\">|<\/span>\n\nLatency<span class=\"w\"> <\/span>distribution:\n<span class=\"w\">  <\/span><span class=\"m\">10<\/span>%<span class=\"w\"> <\/span><span class=\"k\">in<\/span><span class=\"w\"> <\/span><span class=\"m\">0<\/span>.0018<span class=\"w\"> <\/span>secs.\n<span class=\"w\">  <\/span><span class=\"m\">25<\/span>%<span class=\"w\"> <\/span><span class=\"k\">in<\/span><span class=\"w\"> <\/span><span class=\"m\">0<\/span>.0023<span class=\"w\"> <\/span>secs.\n<span class=\"w\">  <\/span><span class=\"m\">50<\/span>%<span class=\"w\"> <\/span><span class=\"k\">in<\/span><span class=\"w\"> <\/span><span class=\"m\">0<\/span>.0030<span class=\"w\"> <\/span>secs.\n<span class=\"w\">  <\/span><span class=\"m\">75<\/span>%<span class=\"w\"> <\/span><span class=\"k\">in<\/span><span class=\"w\"> <\/span><span class=\"m\">0<\/span>.0039<span class=\"w\"> <\/span>secs.\n<span class=\"w\">  <\/span><span class=\"m\">90<\/span>%<span class=\"w\"> <\/span><span class=\"k\">in<\/span><span class=\"w\"> <\/span><span class=\"m\">0<\/span>.0049<span class=\"w\"> <\/span>secs.\n<span class=\"w\">  <\/span><span class=\"m\">95<\/span>%<span class=\"w\"> <\/span><span class=\"k\">in<\/span><span class=\"w\"> <\/span><span class=\"m\">0<\/span>.0058<span class=\"w\"> <\/span>secs.\n<span class=\"w\">  <\/span><span class=\"m\">99<\/span>%<span class=\"w\"> <\/span><span class=\"k\">in<\/span><span class=\"w\"> <\/span><span class=\"m\">0<\/span>.0087<span class=\"w\"> <\/span>secs.\n<span class=\"w\">  <\/span><span class=\"m\">99<\/span>.9%<span class=\"w\"> <\/span><span class=\"k\">in<\/span><span class=\"w\"> <\/span><span class=\"m\">0<\/span>.0126<span class=\"w\"> <\/span>secs.\n<\/pre><\/div>\n<\/div>\n<div class=\"section\" id=\"results-on-disk\">\n<h4>Results on disk<\/h4>\n<div class=\"highlight\"><pre><span><\/span><span class=\"m\">4000000<\/span><span class=\"w\"> <\/span>\/<span class=\"w\"> <\/span><span class=\"m\">4000000<\/span><span class=\"w\">  <\/span><span class=\"m\">100<\/span>.00%<span class=\"w\"> <\/span>4m14ss\n\nSummary:\n<span class=\"w\">  <\/span>Total:<span class=\"w\">        <\/span><span class=\"m\">254<\/span>.7063<span class=\"w\"> <\/span>secs.\n<span class=\"w\">  <\/span>Slowest:<span class=\"w\">      <\/span><span class=\"m\">0<\/span>.2208<span class=\"w\"> <\/span>secs.\n<span class=\"w\">  <\/span>Fastest:<span class=\"w\">      <\/span><span class=\"m\">0<\/span>.0007<span class=\"w\"> <\/span>secs.\n<span class=\"w\">  <\/span>Average:<span class=\"w\">      <\/span><span class=\"m\">0<\/span>.0063<span class=\"w\"> <\/span>secs.\n<span class=\"w\">  <\/span>Stddev:<span class=\"w\">       <\/span><span class=\"m\">0<\/span>.0053<span class=\"w\"> <\/span>secs.\n<span class=\"w\">  <\/span>Requests\/sec:<span class=\"w\"> <\/span><span class=\"m\">15704<\/span>.3628\n\nResponse<span class=\"w\"> <\/span><span class=\"nb\">time<\/span><span class=\"w\"> <\/span>histogram:\n<span class=\"w\">  <\/span><span class=\"m\">0<\/span>.0007<span class=\"w\"> <\/span><span class=\"o\">[<\/span><span class=\"m\">1<\/span><span class=\"o\">]<\/span><span class=\"w\">    <\/span><span class=\"p\">|<\/span>\n<span class=\"w\">  <\/span><span class=\"m\">0<\/span>.0227<span class=\"w\"> <\/span><span class=\"o\">[<\/span><span class=\"m\">3964476<\/span><span class=\"o\">]<\/span><span class=\"w\">      <\/span><span class=\"p\">|<\/span>\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\u220e\n<span class=\"w\">  <\/span><span class=\"m\">0<\/span>.0447<span class=\"w\"> <\/span><span class=\"o\">[<\/span><span class=\"m\">23334<\/span><span class=\"o\">]<\/span><span class=\"w\">        <\/span><span class=\"p\">|<\/span>\n<span class=\"w\">  <\/span><span class=\"m\">0<\/span>.0667<span class=\"w\"> <\/span><span class=\"o\">[<\/span><span class=\"m\">6676<\/span><span class=\"o\">]<\/span><span class=\"w\"> <\/span><span class=\"p\">|<\/span>\n<span class=\"w\">  <\/span><span class=\"m\">0<\/span>.0887<span class=\"w\"> <\/span><span class=\"o\">[<\/span><span class=\"m\">2932<\/span><span class=\"o\">]<\/span><span class=\"w\"> <\/span><span class=\"p\">|<\/span>\n<span class=\"w\">  <\/span><span class=\"m\">0<\/span>.1108<span class=\"w\"> <\/span><span class=\"o\">[<\/span><span class=\"m\">782<\/span><span class=\"o\">]<\/span><span class=\"w\">  <\/span><span class=\"p\">|<\/span>\n<span class=\"w\">  <\/span><span class=\"m\">0<\/span>.1328<span class=\"w\"> <\/span><span class=\"o\">[<\/span><span class=\"m\">639<\/span><span class=\"o\">]<\/span><span class=\"w\">  <\/span><span class=\"p\">|<\/span>\n<span class=\"w\">  <\/span><span class=\"m\">0<\/span>.1548<span class=\"w\"> <\/span><span class=\"o\">[<\/span><span class=\"m\">259<\/span><span class=\"o\">]<\/span><span class=\"w\">  <\/span><span class=\"p\">|<\/span>\n<span class=\"w\">  <\/span><span class=\"m\">0<\/span>.1768<span class=\"w\"> <\/span><span class=\"o\">[<\/span><span class=\"m\">672<\/span><span class=\"o\">]<\/span><span class=\"w\">  <\/span><span class=\"p\">|<\/span>\n<span class=\"w\">  <\/span><span class=\"m\">0<\/span>.1988<span class=\"w\"> <\/span><span class=\"o\">[<\/span><span class=\"m\">178<\/span><span class=\"o\">]<\/span><span class=\"w\">  <\/span><span class=\"p\">|<\/span>\n<span class=\"w\">  <\/span><span class=\"m\">0<\/span>.2208<span class=\"w\"> <\/span><span class=\"o\">[<\/span><span class=\"m\">51<\/span><span class=\"o\">]<\/span><span class=\"w\">   <\/span><span class=\"p\">|<\/span>\n\nLatency<span class=\"w\"> <\/span>distribution:\n<span class=\"w\">  <\/span><span class=\"m\">10<\/span>%<span class=\"w\"> <\/span><span class=\"k\">in<\/span><span class=\"w\"> <\/span><span class=\"m\">0<\/span>.0038<span class=\"w\"> <\/span>secs.\n<span class=\"w\">  <\/span><span class=\"m\">25<\/span>%<span class=\"w\"> <\/span><span class=\"k\">in<\/span><span class=\"w\"> <\/span><span class=\"m\">0<\/span>.0045<span class=\"w\"> <\/span>secs.\n<span class=\"w\">  <\/span><span class=\"m\">50<\/span>%<span class=\"w\"> <\/span><span class=\"k\">in<\/span><span class=\"w\"> <\/span><span class=\"m\">0<\/span>.0055<span class=\"w\"> <\/span>secs.\n<span class=\"w\">  <\/span><span class=\"m\">75<\/span>%<span class=\"w\"> <\/span><span class=\"k\">in<\/span><span class=\"w\"> <\/span><span class=\"m\">0<\/span>.0068<span class=\"w\"> <\/span>secs.\n<span class=\"w\">  <\/span><span class=\"m\">90<\/span>%<span class=\"w\"> <\/span><span class=\"k\">in<\/span><span class=\"w\"> <\/span><span class=\"m\">0<\/span>.0090<span class=\"w\"> <\/span>secs.\n<span class=\"w\">  <\/span><span class=\"m\">95<\/span>%<span class=\"w\"> <\/span><span class=\"k\">in<\/span><span class=\"w\"> <\/span><span class=\"m\">0<\/span>.0109<span class=\"w\"> <\/span>secs.\n<span class=\"w\">  <\/span><span class=\"m\">99<\/span>%<span class=\"w\"> <\/span><span class=\"k\">in<\/span><span class=\"w\"> <\/span><span class=\"m\">0<\/span>.0211<span class=\"w\"> <\/span>secs.\n<span class=\"w\">  <\/span><span class=\"m\">99<\/span>.9%<span class=\"w\"> <\/span><span class=\"k\">in<\/span><span class=\"w\"> <\/span><span class=\"m\">0<\/span>.0753<span class=\"w\"> <\/span>secs.\n<\/pre><\/div>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"section\" id=\"how-to-handle-such-issues\">\n<h2>How to handle such issues<\/h2>\n<p>To handle that problem, we decided to do two things at the same time, especially\nfor the CI tests, which are:<\/p>\n<ul class=\"simple\">\n<li>check if moving etcd to the ramdisk will help<\/li>\n<li>improve sf-operator, to retry updating the object when it causes an error<\/li>\n<\/ul>\n<div class=\"section\" id=\"moving-etcd-to-the-ramdisk\">\n<h3>Moving etcd to the ramdisk<\/h3>\n<p>As it was mentioned, we are using a MicroShift for deploying Kubernetes.\nenvironment. With that <a class=\"reference external\" href=\"https:\/\/github.com\/openstack-k8s-operators\/ansible-microshift-role\/pull\/41\">commit<\/a>,\nwe added a feature to put the etcd on the ramdisk.\nWe did not perform any tests to see if the result would be better, but we did not\nsaw any error related to the etcd anymore.<\/p>\n<\/div>\n<div class=\"section\" id=\"hypervisor-stats\">\n<h3>Hypervisor stats<\/h3>\n<p>We have done an experiment to see how the hypervisor (L0 host) stats look\nlike with etcd on the disk and on ramdisk.<\/p>\n<p>NOTE:\nIt was very difficult to provide good, equal visualization for both\nenvironments (ramdisk and disk), because as an OpenStack user, we were\nnot able to block or disable host for future spawning of new instances there.\nIt means that during the tests, it might be a situation where there were few\nother instances on the same host, which might use a disk.<\/p>\n<div class=\"section\" id=\"on-ramdisk-job-has-started-6-46-utc-8-46-cest\">\n<h4>on ramdisk - job has started 6:46 UTC \/ 8:46 CEST<\/h4>\n<p>There are only 2 instances spawned on same host<\/p>\n<img alt=\"instancesCount\" src=\"images\/etcd\/ramdisk\/1.jpg\" \/>\n<div class=\"section\" id=\"cpu-usage-ramdisk\">\n<h5>CPU usage - ramdisk<\/h5>\n<img alt=\"cpuUsage\" src=\"images\/etcd\/ramdisk\/2.jpg\" \/>\n<\/div>\n<div class=\"section\" id=\"disk-usage-ramdisk\">\n<h5>Disk usage - ramdisk<\/h5>\n<img alt=\"diskUsage\" src=\"images\/etcd\/ramdisk\/3.jpg\" \/>\n<\/div>\n<div class=\"section\" id=\"alternative-visualizations-for-cpu-ramdisk\">\n<h5>Alternative visualizations for CPU - ramdisk<\/h5>\n<img alt=\"cpuUsageAlt\" src=\"images\/etcd\/ramdisk\/4.jpg\" \/>\n<\/div>\n<div class=\"section\" id=\"alternative-visualization-for-disk-ramdisk\">\n<h5>Alternative visualization for disk - ramdisk<\/h5>\n<img alt=\"diskUsageAlt\" src=\"images\/etcd\/ramdisk\/5.jpg\" \/>\n<p>and<\/p>\n<img alt=\"diskUsageAlt2\" src=\"images\/etcd\/ramdisk\/6.jpg\" \/>\n<\/div>\n<\/div>\n<div class=\"section\" id=\"on-disk-job-has-started-6-18-utc-8-18-cest\">\n<h4>2. on disk - job has started 6:18 UTC \/ 8:18 CEST<\/h4>\n<p>There are 3 instances spawned on same host. There were also one more\nVM, but it should not affect in tests results.<\/p>\n<div class=\"section\" id=\"cpu-usage-disk\">\n<h5>CPU usage - disk<\/h5>\n<img alt=\"cpuUsage\" src=\"images\/etcd\/disk\/1.jpg\" \/>\n<\/div>\n<div class=\"section\" id=\"disk-usage-disk\">\n<h5>Disk usage - disk<\/h5>\n<img alt=\"diskUsage\" src=\"images\/etcd\/disk\/2.jpg\" \/>\n<\/div>\n<div class=\"section\" id=\"alternative-visualizations-for-cpu-disk\">\n<h5>Alternative visualizations for CPU - disk<\/h5>\n<img alt=\"cpuUsageAlt\" src=\"images\/etcd\/disk\/3.jpg\" \/>\n<\/div>\n<div class=\"section\" id=\"alternative-visualization-for-disk-disk\">\n<h5>Alternative visualization for disk - disk<\/h5>\n<img alt=\"diskUsageAlt\" src=\"images\/etcd\/disk\/4.jpg\" \/>\n<p>and<\/p>\n<img alt=\"diskUsageAlt2\" src=\"images\/etcd\/disk\/5.jpg\" \/>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"section\" id=\"improvements-in-sf-operator\">\n<h3>Improvements in sf-operator<\/h3>\n<p>The main issue while running the reconcile loop was that the object should be\nupdated, but it was not because of the high etcd (storage) utilization.<\/p>\n<p>More about that issue will be explained in the next blog post.<\/p>\n<\/div>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2023 Sep 08 to Sep 27 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2023-sep-08-to-sep-27-summary.html","rel":"alternate"}},"published":"2023-09-27T10:00:00+00:00","updated":"2023-09-27T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2023-09-27:\/sprint-2023-sep-08-to-sep-27-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We check issue what community has with zuul-operator change, that the CI job is running on the minikube. We spotted few issue and propose few patches with a fix, but still it does not pass CI<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory \u2026<\/h2><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We check issue what community has with zuul-operator change, that the CI job is running on the minikube. We spotted few issue and propose few patches with a fix, but still it does not pass CI<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We added debootstrap to the nodepool-builder container.<\/li>\n<li>We Added zuul-client command to sfconfig tool<\/li>\n<li>We added Status Conditions to all sf-operator services<\/li>\n<li>We Added Zuul Bootstrap Zuul Tenant Config subcommand to sfconfig cli for ease of use to create zuul pipelines, jobs and playbooks<\/li>\n<li>We have started to Add GitHub and GitLab connection support to Zuul Connections in sf-operator<\/li>\n<li>Most of the monitoring stack was merged into sf-operator, adding statsd exporters to zuul and nodepool require a non-trivial rebase + documentation<\/li>\n<li>We investigated options for log aggregation and collection best practices with the MicroShift and Logging Operator teams<\/li>\n<li>We gave the documentation a big fat makeover: the doc is now split by roles (operator installer, deployer, user, developer) and also has short &quot;getting started&quot; tutorials for each section. A lot of deprecated\/obsolete info was reworked. A CLI reference was also added.<\/li>\n<li>We made a QoL change to how namespace events get collected in our CI: they're now in chronological order and with absolute timestamps o\/<\/li>\n<li>We wrote and grommed several stories for the next milestone of sf-operator<\/li>\n<li>We fixed (for zuul 9.1.0) The 'require-approval' trigger attribute is deprecated.  Use 'require' instead. <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29405\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29405<\/a><\/li>\n<li>We wrote ADR 10 about zuul-operator choice <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29397\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29397<\/a><\/li>\n<li>We enabled one job on zuul.microshift.sf.io triggered from sf-operator change<\/li>\n<li>We provided a command save and dump nodepool-provider-secrets content from sfconfig.yaml<\/li>\n<li>We fixed job-output zuul does not redirect to the .gz file job-output.txt is 404 <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29474\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29474<\/a><\/li>\n<li>We refined the rbac used by service account config-update <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29480\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29480<\/a><\/li>\n<li>We refectored (removing duplicated code), sanitized (CamelCase) ,splitted some part of the code in re-usable GO packages  <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29527\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29527<\/a><\/li>\n<li>We added staticcheck linting <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29502\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29502<\/a><\/li>\n<li>We started to add support for Nodepool builder<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Logreduce WASM based web interface","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/logreduce-wasm-based-web-interface.html","rel":"alternate"}},"published":"2023-09-18T00:00:00+00:00","updated":"2023-09-18T00:00:00+00:00","author":{"name":"tristanC"},"id":"tag:www.softwarefactory-project.io,2023-09-18:\/logreduce-wasm-based-web-interface.html","summary":"<style type=\"text\/css\">\n  blockquote {\n    font-size: small;\n    padding: 0px 5px;\n  }\n<\/style><!-- This work is licensed under the Creative Commons Attribution 4.0 International License.\n     To view a copy of this license, visit http:\/\/creativecommons.org\/licenses\/by\/4.0\/\n     or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.\n--><p>This post introduces a new Web Assembly (WASM) based web interface to\nvisualize <a class=\"reference external\" href=\"https:\/\/github.com\/logreduce\/logreduce#readme\">logreduce<\/a>'s reports.<\/p>\n<p>In three parts, I present:<\/p>\n<ul class=\"simple\">\n<li>The logreduce report format.<\/li>\n<li>WASM and Web APIs.<\/li>\n<li>HTML macro examples.<\/li>\n<\/ul>\n<div class=\"section\" id=\"context-and-problem-statement\">\n<h2>Context and problem statement<\/h2>\n<p>Logreduce produces a <a class=\"reference external\" href=\"https:\/\/github.com\/logreduce\/logreduce\/blob\/main\/crates\/report\/src\/report.rs\">report<\/a> containing the anomalies context \u2026<\/p><\/div>","content":"<style type=\"text\/css\">\n  blockquote {\n    font-size: small;\n    padding: 0px 5px;\n  }\n<\/style><!-- This work is licensed under the Creative Commons Attribution 4.0 International License.\n     To view a copy of this license, visit http:\/\/creativecommons.org\/licenses\/by\/4.0\/\n     or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.\n--><p>This post introduces a new Web Assembly (WASM) based web interface to\nvisualize <a class=\"reference external\" href=\"https:\/\/github.com\/logreduce\/logreduce#readme\">logreduce<\/a>'s reports.<\/p>\n<p>In three parts, I present:<\/p>\n<ul class=\"simple\">\n<li>The logreduce report format.<\/li>\n<li>WASM and Web APIs.<\/li>\n<li>HTML macro examples.<\/li>\n<\/ul>\n<div class=\"section\" id=\"context-and-problem-statement\">\n<h2>Context and problem statement<\/h2>\n<p>Logreduce produces a <a class=\"reference external\" href=\"https:\/\/github.com\/logreduce\/logreduce\/blob\/main\/crates\/report\/src\/report.rs\">report<\/a> containing the anomalies context along\nwith metadata such as read errors and unknown files. By default the\nreport is printed to the standard output, but it can also be saved as an\nHTML file.<\/p>\n<p>The HTML export is significantly larger than the report itself. Indeed,\neach log line needs to be wrapped inside an HTML element, while the\nreport can be tightly packed using the <a class=\"reference external\" href=\"https:\/\/github.com\/bincode-org\/bincode#readme\">bincode<\/a> serialization format.\nMoreover the HTML export is static and it can't be re-imported, for\nexample to search for known anomalies.<\/p>\n<p>In the next sections, I present a more efficient implementation using\nWASM.<\/p>\n<\/div>\n<div class=\"section\" id=\"web-assembly-wasm\">\n<h2>Web Assembly (WASM)<\/h2>\n<p>WASM is a portable binary instruction format that can be executed by web\nclients like javascript. Since Rust has extensive support for this\ncompilation target, I decided to implement the logreduce report\ninterface with WASM. The main benefits are:<\/p>\n<ul class=\"simple\">\n<li>Leverage Rust type system.<\/li>\n<li>Re-use the existing logreduce's source code.<\/li>\n<\/ul>\n<!--  -->\n<blockquote>\nCheckout the <a class=\"reference external\" href=\"https:\/\/rustwasm.github.io\/docs\/book\/\">rustwasm book<\/a> to learn more.<\/blockquote>\n<p>In the next sections I present how to create a web application.<\/p>\n<\/div>\n<div class=\"section\" id=\"web-apis\">\n<h2>Web APIs<\/h2>\n<p>WASM programs don't have direct access to the Web APIs. For Rust, the\n<a class=\"reference external\" href=\"https:\/\/docs.rs\/web-sys\">web-sys<\/a> library provides the necessary bindings to manipulate the\nDOM. Here is how the hello world demo looks like:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"cp\">#[wasm_bindgen(start)]<\/span>\n<span class=\"k\">pub<\/span><span class=\"w\"> <\/span><span class=\"k\">fn<\/span><span class=\"w\"> <\/span><span class=\"nf\">run<\/span><span class=\"p\">()<\/span><span class=\"w\"> <\/span><span class=\"p\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"nb\">Result<\/span><span class=\"o\">&lt;<\/span><span class=\"p\">(),<\/span><span class=\"w\"> <\/span><span class=\"n\">JsValue<\/span><span class=\"o\">&gt;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">window<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">web_sys<\/span><span class=\"p\">::<\/span><span class=\"n\">window<\/span><span class=\"p\">().<\/span><span class=\"n\">expect<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;no global `window` exists&quot;<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">document<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">window<\/span><span class=\"p\">.<\/span><span class=\"n\">document<\/span><span class=\"p\">().<\/span><span class=\"n\">expect<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;should have a document on window&quot;<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">body<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">document<\/span><span class=\"p\">.<\/span><span class=\"n\">body<\/span><span class=\"p\">().<\/span><span class=\"n\">expect<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;document should have a body&quot;<\/span><span class=\"p\">);<\/span>\n\n<span class=\"w\">    <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">val<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">document<\/span><span class=\"p\">.<\/span><span class=\"n\">create_element<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;p&quot;<\/span><span class=\"p\">)<\/span><span class=\"o\">?<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">val<\/span><span class=\"p\">.<\/span><span class=\"n\">set_text_content<\/span><span class=\"p\">(<\/span><span class=\"nb\">Some<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Hello from Rust!&quot;<\/span><span class=\"p\">));<\/span>\n\n<span class=\"w\">    <\/span><span class=\"n\">body<\/span><span class=\"p\">.<\/span><span class=\"n\">append_child<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"n\">val<\/span><span class=\"p\">)<\/span><span class=\"o\">?<\/span><span class=\"p\">;<\/span>\n\n<span class=\"w\">    <\/span><span class=\"nb\">Ok<\/span><span class=\"p\">(())<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<!--  -->\n<blockquote>\nCheckout the <a class=\"reference external\" href=\"https:\/\/rustwasm.github.io\/wasm-bindgen\/\">wasm-bindgen documentation<\/a> to learn more.<\/blockquote>\n<p>Similar to React and Angular, there are libraries built on top of\nweb-sys to implement higher level APIs.<\/p>\n<p>In the next section I present the libraries I used.<\/p>\n<\/div>\n<div class=\"section\" id=\"functional-reactive-programming-frp\">\n<h2>Functional Reactive Programming (FRP)<\/h2>\n<p>In an earlier implementation, I used <a class=\"reference external\" href=\"https:\/\/yew.rs\/\">yew<\/a> which provides a React style\nAPI. Here is how the report was being fetched:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"cp\">#[function_component(App)]<\/span>\n<span class=\"k\">fn<\/span><span class=\"w\"> <\/span><span class=\"nf\">app<\/span><span class=\"p\">()<\/span><span class=\"w\"> <\/span><span class=\"p\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"nc\">Html<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">report<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nc\">UseStateHandle<\/span><span class=\"o\">&lt;<\/span><span class=\"nb\">Option<\/span><span class=\"o\">&lt;<\/span><span class=\"nb\">Result<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">Report<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nb\">String<\/span><span class=\"o\">&gt;&gt;&gt;<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">use_state<\/span><span class=\"p\">(<\/span><span class=\"o\">||<\/span><span class=\"w\"> <\/span><span class=\"nb\">None<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">report<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">report<\/span><span class=\"p\">.<\/span><span class=\"n\">clone<\/span><span class=\"p\">();<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">use_effect_with_deps<\/span><span class=\"p\">(<\/span>\n<span class=\"w\">            <\/span><span class=\"k\">move<\/span><span class=\"w\"> <\/span><span class=\"o\">|<\/span><span class=\"n\">_<\/span><span class=\"o\">|<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">report<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">report<\/span><span class=\"p\">.<\/span><span class=\"n\">clone<\/span><span class=\"p\">();<\/span>\n<span class=\"w\">                <\/span><span class=\"n\">wasm_bindgen_futures<\/span><span class=\"p\">::<\/span><span class=\"n\">spawn_local<\/span><span class=\"p\">(<\/span><span class=\"k\">async<\/span><span class=\"w\"> <\/span><span class=\"k\">move<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                    <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">result<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">get_report<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;report.bin&quot;<\/span><span class=\"p\">).<\/span><span class=\"k\">await<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">                    <\/span><span class=\"n\">report<\/span><span class=\"p\">.<\/span><span class=\"n\">set<\/span><span class=\"p\">(<\/span><span class=\"nb\">Some<\/span><span class=\"p\">(<\/span><span class=\"n\">result<\/span><span class=\"p\">));<\/span>\n<span class=\"w\">                <\/span><span class=\"p\">});<\/span>\n<span class=\"w\">                <\/span><span class=\"o\">||<\/span><span class=\"w\"> <\/span><span class=\"p\">()<\/span>\n<span class=\"w\">            <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">            <\/span><span class=\"p\">(),<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n\n<span class=\"w\">    <\/span><span class=\"k\">match<\/span><span class=\"w\"> <\/span><span class=\"n\">report<\/span><span class=\"p\">.<\/span><span class=\"n\">deref<\/span><span class=\"p\">()<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"nb\">Some<\/span><span class=\"p\">(<\/span><span class=\"nb\">Ok<\/span><span class=\"p\">(<\/span><span class=\"n\">report<\/span><span class=\"p\">))<\/span><span class=\"w\"> <\/span><span class=\"o\">=&gt;<\/span><span class=\"w\"> <\/span><span class=\"n\">render_report<\/span><span class=\"p\">(<\/span><span class=\"n\">report<\/span><span class=\"p\">),<\/span>\n<span class=\"w\">        <\/span><span class=\"nb\">Some<\/span><span class=\"p\">(<\/span><span class=\"nb\">Err<\/span><span class=\"p\">(<\/span><span class=\"n\">err<\/span><span class=\"p\">))<\/span><span class=\"w\"> <\/span><span class=\"o\">=&gt;<\/span><span class=\"w\"> <\/span><span class=\"n\">html<\/span><span class=\"o\">!<\/span><span class=\"p\">(<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">div<\/span><span class=\"o\">&gt;<\/span><span class=\"p\">{<\/span><span class=\"n\">err<\/span><span class=\"p\">}<\/span><span class=\"o\">&lt;\/<\/span><span class=\"n\">div<\/span><span class=\"o\">&gt;<\/span><span class=\"p\">),<\/span>\n<span class=\"w\">        <\/span><span class=\"nb\">None<\/span><span class=\"w\"> <\/span><span class=\"o\">=&gt;<\/span><span class=\"w\"> <\/span><span class=\"n\">html<\/span><span class=\"o\">!<\/span><span class=\"p\">(<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">div<\/span><span class=\"o\">&gt;<\/span><span class=\"p\">{<\/span><span class=\"s\">&quot;loading...&quot;<\/span><span class=\"p\">}<\/span><span class=\"o\">&lt;\/<\/span><span class=\"n\">div<\/span><span class=\"o\">&gt;<\/span><span class=\"p\">),<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">};<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<p>Unfortunately such <tt class=\"docutils literal\">use<\/tt> hooks only work inside components and they\nrequire a bit of boilerplate to pass properties. Instead I switched to a\nlower level library named <a class=\"reference external\" href=\"https:\/\/github.com\/Pauan\/rust-dominator#readme\">dominator<\/a> which provides FRP APIs I find\neasier to work with.<\/p>\n<p>Here is the equivalent code where the hooks are replaced with a signal:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"k\">struct<\/span><span class=\"w\"> <\/span><span class=\"nc\">App<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">report<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nc\">Mutable<\/span><span class=\"o\">&lt;<\/span><span class=\"nb\">Option<\/span><span class=\"o\">&lt;<\/span><span class=\"nb\">Result<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">Report<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nb\">String<\/span><span class=\"o\">&gt;&gt;&gt;<\/span><span class=\"p\">,<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"k\">pub<\/span><span class=\"w\"> <\/span><span class=\"k\">fn<\/span><span class=\"w\"> <\/span><span class=\"nf\">main<\/span><span class=\"p\">()<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">console_error_panic_hook<\/span><span class=\"p\">::<\/span><span class=\"n\">set_once<\/span><span class=\"p\">();<\/span>\n<span class=\"w\">    <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">app<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">Arc<\/span><span class=\"p\">::<\/span><span class=\"n\">new<\/span><span class=\"p\">(<\/span><span class=\"n\">App<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"n\">report<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nc\">Mutable<\/span><span class=\"p\">::<\/span><span class=\"n\">new<\/span><span class=\"p\">(<\/span><span class=\"nb\">None<\/span><span class=\"p\">)});<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">spawn_local<\/span><span class=\"p\">(<\/span><span class=\"n\">clone<\/span><span class=\"o\">!<\/span><span class=\"p\">(<\/span><span class=\"n\">app<\/span><span class=\"w\"> <\/span><span class=\"o\">=&gt;<\/span><span class=\"w\"> <\/span><span class=\"k\">async<\/span><span class=\"w\"> <\/span><span class=\"k\">move<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">result<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">get_report<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;report.bin&quot;<\/span><span class=\"p\">).<\/span><span class=\"k\">await<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">app<\/span><span class=\"p\">.<\/span><span class=\"n\">report<\/span><span class=\"p\">.<\/span><span class=\"n\">replace<\/span><span class=\"p\">(<\/span><span class=\"nb\">Some<\/span><span class=\"p\">(<\/span><span class=\"n\">result<\/span><span class=\"p\">));<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}));<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">dominator<\/span><span class=\"p\">::<\/span><span class=\"n\">append_dom<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"n\">dominator<\/span><span class=\"p\">::<\/span><span class=\"n\">body<\/span><span class=\"p\">(),<\/span><span class=\"w\"> <\/span><span class=\"n\">render_app<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"n\">app<\/span><span class=\"p\">));<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"k\">fn<\/span><span class=\"w\"> <\/span><span class=\"nf\">render_app<\/span><span class=\"p\">(<\/span><span class=\"n\">state<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"kp\">&amp;<\/span><span class=\"nc\">Arc<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">App<\/span><span class=\"o\">&gt;<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"nc\">Dom<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">html<\/span><span class=\"o\">!<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;div&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"p\">{.<\/span><span class=\"n\">child_signal<\/span><span class=\"p\">(<\/span><span class=\"n\">state<\/span><span class=\"p\">.<\/span><span class=\"n\">report<\/span><span class=\"p\">.<\/span><span class=\"n\">signal_ref<\/span><span class=\"p\">(<\/span><span class=\"o\">|<\/span><span class=\"n\">data<\/span><span class=\"o\">|<\/span><span class=\"w\"> <\/span><span class=\"nb\">Some<\/span><span class=\"p\">(<\/span><span class=\"k\">match<\/span><span class=\"w\"> <\/span><span class=\"n\">data<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"nb\">Some<\/span><span class=\"p\">(<\/span><span class=\"nb\">Ok<\/span><span class=\"p\">(<\/span><span class=\"n\">report<\/span><span class=\"p\">))<\/span><span class=\"w\"> <\/span><span class=\"o\">=&gt;<\/span><span class=\"w\"> <\/span><span class=\"n\">render_report<\/span><span class=\"p\">(<\/span><span class=\"n\">report<\/span><span class=\"p\">),<\/span>\n<span class=\"w\">        <\/span><span class=\"nb\">Some<\/span><span class=\"p\">(<\/span><span class=\"nb\">Err<\/span><span class=\"p\">(<\/span><span class=\"n\">err<\/span><span class=\"p\">))<\/span><span class=\"w\"> <\/span><span class=\"o\">=&gt;<\/span><span class=\"w\"> <\/span><span class=\"n\">html<\/span><span class=\"o\">!<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;div&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"p\">{.<\/span><span class=\"n\">children<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"k\">mut<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"n\">text<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Error: &quot;<\/span><span class=\"p\">),<\/span><span class=\"w\"> <\/span><span class=\"n\">text<\/span><span class=\"p\">(<\/span><span class=\"n\">err<\/span><span class=\"p\">)])}),<\/span>\n<span class=\"w\">        <\/span><span class=\"nb\">None<\/span><span class=\"w\"> <\/span><span class=\"o\">=&gt;<\/span><span class=\"w\"> <\/span><span class=\"n\">html<\/span><span class=\"o\">!<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;div&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"p\">{.<\/span><span class=\"n\">text<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;loading...&quot;<\/span><span class=\"p\">)}),<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">})))})<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<p>Even though the <em>html!<\/em> macro is less pretty, it is much more flexible\nas it lets you build reactive elements without relying on a virtual DOM\nand clunky properties.<\/p>\n<\/div>\n<div class=\"section\" id=\"conclusion\">\n<h2>Conclusion<\/h2>\n<p>Thanks to the Rust WASM target, logreduce can now produce efficient\nreports with this <a class=\"reference external\" href=\"https:\/\/github.com\/logreduce\/logreduce\/pull\/25\">PR#25<\/a>. For a typical CI build, the report size is\nreduced from a 881 KiB report.html, down to a 148 KiB report.bin. The\nWASM program that decodes the binary report is 525 KiB, which combined\nwith the binary report, is still smaller than the static HTML. Moverover\nthe payload can be hosted on a content delivery network so that it is\nre-used with every report.<\/p>\n<img alt=\"wasm-size\" src=\".\/images\/logreduce-wasm-size.png\" \/>\n<p>WASM is a fascinating system, and I am looking forward making more use\nof it. In particular, it would be interesting to compile the whole\nlogreduce process to produce the report directly on the client side.<\/p>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2023 Aug 18 to Sep 06 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2023-aug-18-to-sep-06-summary.html","rel":"alternate"}},"published":"2023-09-06T10:00:00+00:00","updated":"2023-09-06T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2023-09-06:\/sprint-2023-aug-18-to-sep-06-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We updated the zuul-operator switch to quay, but it is now failing with a dns issue with the perconna operator.<\/li>\n<li>We proposed to add connection health metric to zuul.<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>operator - We have implemented the support for \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We updated the zuul-operator switch to quay, but it is now failing with a dns issue with the perconna operator.<\/li>\n<li>We proposed to add connection health metric to zuul.<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>operator - We have implemented the support for Custom Certificate for Route<\/li>\n<li>operator - We have implemented the support for LetsEncrypt Certificate for Route<\/li>\n<li>operator - We have ensured that SF on microshift.sf.io got certificates via cert-manager and sf-operator 0.0.4 (<a class=\"reference external\" href=\"https:\/\/zuul.microshift.softwarefactory-project.io\/tenants\">https:\/\/zuul.microshift.softwarefactory-project.io\/tenants<\/a>)<\/li>\n<li>We integrated Gerrit 3.7 to sf-3.8.<\/li>\n<li>We updated zuul and nodepool in sf-3.8, which required python3.11.<\/li>\n<li>We improved zuul-weeder to report config loading error in the web ui.<\/li>\n<li>operator - we've made good progress on the monitoring stack: add a cli command to deploy a demo prometheus, add automatic collection of metrics for zuul and logserver + default alerting<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2023 Jul 28 to Aug 16 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2023-jul-28-to-aug-16-summary.html","rel":"alternate"}},"published":"2023-08-16T10:00:00+00:00","updated":"2023-08-16T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2023-08-16:\/sprint-2023-jul-28-to-aug-16-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<p># Software Factory<\/p>\n<ul class=\"simple\">\n<li>We validated and added documentation to add clouds.yaml and add cloud images on nodepool<\/li>\n<li>We added a workaround to avoid rate-limit issue with github when we setup OLM <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29118\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory \u2026<\/a><\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<p># Software Factory<\/p>\n<ul class=\"simple\">\n<li>We validated and added documentation to add clouds.yaml and add cloud images on nodepool<\/li>\n<li>We added a workaround to avoid rate-limit issue with github when we setup OLM <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29118\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29118<\/a><\/li>\n<li>We improved the sfconfig cli to deploy gerrit, install the requirements setup the local copy of the config repositories and provision a demo tenant ready to be used.<\/li>\n<li>We removed unused policy code from managesf.<\/li>\n<li>We mitigated the update panic error happening on the operator controller.<\/li>\n<li>We investigated an issue about statefulset rolout discovered after the config repo bootstrap on microshift.sf.io <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29100\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29100<\/a><\/li>\n<li>We proposed a change to remove the zuul and nodepool sidecar containers (config-update must use the main container instead) <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29125\/\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29125\/<\/a><\/li>\n<li>We proposed a config-check negative test for Zuul and Nodepool <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29146\/\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/29146\/<\/a><\/li>\n<li>We added a post step to get service logs due from time to time CI gates are flapping<\/li>\n<li>We added a feature to enable custom SSL certificate for the route<\/li>\n<li>We perform a test if sf-operator will be working with Microshift 4.14 - all is fine, we can think to update the Microshift version in our CI<\/li>\n<li>We added zuul-client to zuul images and the corresponding sfconfig subcommand<\/li>\n<li>We added some subcommands to sfconfig tool:\n* Create Zuul Tenant config file\n* Add Zuul Teanant connection\n* Create Zuul Job\n* Create Zuul Pipeline<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2023 Jul 07 to Jul 26 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2023-jul-07-to-jul-26-summary.html","rel":"alternate"}},"published":"2023-07-26T10:00:00+00:00","updated":"2023-07-26T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2023-07-26:\/sprint-2023-jul-07-to-jul-26-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We reviewed some zuul changes<\/li>\n<li>We propose a change for zuul related to the elasticsearch driver to send the docs to the Opensearch as integer fields instead of string<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We improved the microshift driver integration for \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We reviewed some zuul changes<\/li>\n<li>We propose a change for zuul related to the elasticsearch driver to send the docs to the Opensearch as integer fields instead of string<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We improved the microshift driver integration for nodepool by supporting multiple context and namespace<\/li>\n<li>We investigated sf-ci flakyness<\/li>\n<li>We added a new <cite>sfconfig<\/cite> command line entrypoint to deploy the sf-operator<\/li>\n<li>We wrote an ADR for config jobs and finish the implementation using Ansible tasks<\/li>\n<li>We've completed work on the configcheckjob resource, needs merging<\/li>\n<li>we have experimented with the mariadb operator on microshift, confirming that it works with minimal effort<\/li>\n<li>We added jobs on the config repo to use .\/tools\/sfconfig microshift to deploy microshift on 9-stream instance<\/li>\n<li>We are working to add multinode ci job for sf-operator-dev job<\/li>\n<li>We are finishing to work on sf-4-alpha-2 and grommed sf-4-alpha-3<\/li>\n<li>We set Logserver at root url<\/li>\n<li>We updated Documentation on who to install Software Factory Operator<\/li>\n<li>We improve the way we deploy new versions of Software Factory Operator:<ul>\n<li>Add each new version to the Catalog Index<\/li>\n<li>At each new repository tag<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2023 Jun 16 to Jul 05 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2023-jun-16-to-jul-05-summary.html","rel":"alternate"}},"published":"2023-07-05T10:00:00+00:00","updated":"2023-07-05T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2023-07-05:\/sprint-2023-jun-16-to-jul-05-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We fixed issue that logsender was not removing old directories<\/li>\n<li>We updated services on logscraper01.opendev.org host<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We modify how CI jobs and sfconfig command run, now both use the same playbooks and variables are \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We fixed issue that logsender was not removing old directories<\/li>\n<li>We updated services on logscraper01.opendev.org host<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We modify how CI jobs and sfconfig command run, now both use the same playbooks and variables are loaded during play instead having variables defined in multiple places<\/li>\n<li>We added the bits needed for 9-stream in setup-env roles <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28494\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28494<\/a><\/li>\n<li>We experimented with a custom resource to handle config-checks [Demo maybe]<\/li>\n<li>We can now adjust logserver's loopDelay, retentionDays in a CR, the changes are reconciled automatically.<\/li>\n<li>We start installing Software Factory via Catalog Source and Subscription and We now, generate operator's bundle, operator's catalog and operator container images, publishing then into a registry<\/li>\n<li>We fix the Software Factory Operator's bundle versioning problem realted to Operator's upgrade process<\/li>\n<li>We fix the lack of log files in Zuul Web UI<\/li>\n<li>We created several subcommand for our sfconfig cli command (operator delete, operator create, sf delete)<\/li>\n<li>We changed the logserver logs directory to the root url<\/li>\n<li>We start to write the README file for the end users<\/li>\n<li>We worked on configuring microshift and nodepool to validate running containerized job on the microshift instance<\/li>\n<li>We added a feature to spawn pods on the microshift node via nodepool<\/li>\n<li>We removed the support for managesf related commands<\/li>\n<li>We extracted the Gerrit service but keep it as a side service for CI and DEV<\/li>\n<li>We ensured that the Managed resource (SoftwareFactory) can be spawn w\/o a config repo setup<\/li>\n<li>We experienced with TLS on the route with custom cert: <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28698\/\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28698\/<\/a><\/li>\n<li>We wrote an ADR to write up current understanding and proposal  <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28701\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28701<\/a><\/li>\n<li>We added support nodepool-launcher config-update <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28748\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28748<\/a><\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2023 May 26 to Jun 14 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2023-may-26-to-jun-14-summary.html","rel":"alternate"}},"published":"2023-06-14T10:00:00+00:00","updated":"2023-06-14T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2023-06-14:\/sprint-2023-may-26-to-jun-14-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We fixed issues in logsender that was keeping old directories<\/li>\n<li>We fixed an issue in logsender where memory value was over max int64 value<\/li>\n<li>We added a feature into logsender that is trying few times to download most \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We fixed issues in logsender that was keeping old directories<\/li>\n<li>We fixed an issue in logsender where memory value was over max int64 value<\/li>\n<li>We added a feature into logsender that is trying few times to download most important files to be processed later<\/li>\n<li>We updated Opensearch version to 2.5 in CI jobs in ci-log-processing project<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We bootstraped sfconfig cli tool to deploy microshift if needed and run ci tests<\/li>\n<li>We improved setup-env role to ensure we have all the needed bit from a centos-9-stream vanilla and f38 for microshift and run ci tests<\/li>\n<li>We Introduced a new CustomResource called LogServer and its controller<\/li>\n<li>We added the Software Factory Operator CI Upgrade Job<\/li>\n<li>We updated SF-Operator Upgrade Job to user sfconfig cli command<\/li>\n<li>We enabled the ObservedGeneration pattern <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28556\/\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28556\/<\/a><\/li>\n<li>We added a new field in status reconciledBy: &lt;string&gt; which is the OPERATOR_CONDITION env var value <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28604\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28604<\/a><\/li>\n<li>We are removing Gerrit + managesf to re-focus on essentials components <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28620\/\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28620\/<\/a><\/li>\n<li>We removed some CI flakyness thanks to <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28552\/\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28552\/<\/a><\/li>\n<li>We Added a test for adding\/removing Gerrit connection to Zuul <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28476\/\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28476\/<\/a><\/li>\n<li>We fixed containerfile errors related to the decommissioning of the centos registry, use quay.io instead.<\/li>\n<li>We worked further on metrics for sf-operator (note that these are still open patchsets):<ul>\n<li>we added operator metrics<\/li>\n<li>we finalized the sidecar container to expose volume metrics (logserver)<\/li>\n<li>we exposed zuul metrics<\/li>\n<li>we managed to deploy a prometheus instance in a 'monitoring' namespace with OLM, and automated collection with a serviceMonitor CR<\/li>\n<\/ul>\n<\/li>\n<li>We added a feature into the sf-operator to set the replica count for the statefulset apps<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2023 May 05 to May 24 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2023-may-05-to-may-24-summary.html","rel":"alternate"}},"published":"2023-05-24T10:00:00+00:00","updated":"2023-05-24T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2023-05-24:\/sprint-2023-may-05-to-may-24-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We reviewed a bubblewrap security fix for Zuul<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We created ADR to create a sfconfig cli tool to manage sf-operator <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28318\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28318<\/a><\/li>\n<li>We boostraped sfconfig cli tool to run local \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We reviewed a bubblewrap security fix for Zuul<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We created ADR to create a sfconfig cli tool to manage sf-operator <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28318\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28318<\/a><\/li>\n<li>We boostraped sfconfig cli tool to run local and ci tests <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28319\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28319<\/a><\/li>\n<li>We are working to add sfconfig microshift subcmd to setup microshift on a vanilla 9-stream instance <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28377\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28377<\/a><\/li>\n<li>We are working on using zuul 8.3.1 with sf-operator<\/li>\n<li>We enabled the run operator via &quot;operator-sdk bundle run&quot; in CI <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28276\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28276<\/a><\/li>\n<li>We fixed the empty config repo logs into the artifacts <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28307\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28307<\/a><\/li>\n<li>We published a Blog post about OLM <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/www.softwarefactory-project.io\/+\/28327\">https:\/\/softwarefactory-project.io\/r\/c\/www.softwarefactory-project.io\/+\/28327<\/a><\/li>\n<li>We added a CI job for validating the dev - mode (go run) <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28355\/\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28355\/<\/a><\/li>\n<li>We discussed about whether or not to leverage multiple CR\/Custom controller<\/li>\n<li>We discussed the milestone 2 Stories<\/li>\n<li>We made some progress landing changes for sf 3.8.9<\/li>\n<li>We investigated how to split the sf-operator into multiple controllers<\/li>\n<li>We added Publish OLM role - <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28349\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28349<\/a><\/li>\n<li>We added Publish Operator's Catalog - <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28373\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28373<\/a><\/li>\n<li>We added a make task to generate the Catalog files - <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28388\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28388<\/a><\/li>\n<li>We Changed from cp to rsync command to sync diretories - <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28396\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28396<\/a><\/li>\n<li>We pinned Websocket-client Python package to version 1.5.1 - <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28400\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28400<\/a><\/li>\n<li>We Created directories using file ansible module - <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28402\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28402<\/a><\/li>\n<li>We added Software Factory Operator CI Upgrade Job - <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28378\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28378<\/a><\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Monocle Operator - OLM","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/monocle-operator-olm.html","rel":"alternate"}},"published":"2023-05-15T00:00:00+00:00","updated":"2023-05-15T00:00:00+00:00","author":{"name":"Fabien Boucher"},"id":"tag:www.softwarefactory-project.io,2023-05-15:\/monocle-operator-olm.html","summary":"<style type=\"text\/css\">\n\n  .literal {\n    border-radius: 6px;\n    padding: 1px 1px;\n    background-color: rgba(27,31,35,.05);\n  }\n\n<\/style><p>This post aims to introduce the <a class=\"reference external\" href=\"https:\/\/olm.operatorframework.io\/\">Operator Lifecycle Management (OLM)<\/a>\nand how we integrated the <a class=\"reference external\" href=\"https:\/\/github.com\/change-metrics\/monocle-operator\">Monocle Operator<\/a> as an OLM package into the\n<a class=\"reference external\" href=\"https:\/\/operatorhub.io\">operatorhub.io<\/a> catalog.<\/p>\n<p>This article is a follow up post of &quot;<a class=\"reference external\" href=\"https:\/\/www.softwarefactory-project.io\/monocle-operator-phase-1-basic-install.html\">Monocle Operator - Phase 1 \u2026<\/a><\/p>","content":"<style type=\"text\/css\">\n\n  .literal {\n    border-radius: 6px;\n    padding: 1px 1px;\n    background-color: rgba(27,31,35,.05);\n  }\n\n<\/style><p>This post aims to introduce the <a class=\"reference external\" href=\"https:\/\/olm.operatorframework.io\/\">Operator Lifecycle Management (OLM)<\/a>\nand how we integrated the <a class=\"reference external\" href=\"https:\/\/github.com\/change-metrics\/monocle-operator\">Monocle Operator<\/a> as an OLM package into the\n<a class=\"reference external\" href=\"https:\/\/operatorhub.io\">operatorhub.io<\/a> catalog.<\/p>\n<p>This article is a follow up post of &quot;<a class=\"reference external\" href=\"https:\/\/www.softwarefactory-project.io\/monocle-operator-phase-1-basic-install.html\">Monocle Operator - Phase 1 - Basic\nInstall<\/a>&quot;.<\/p>\n<div class=\"section\" id=\"what-is-olm\">\n<h2>What is OLM<\/h2>\n<p>The Operator Lifecycle Management (OLM) is an approach to simplify\nKubernetes operators deployment, updates and lifecycle management.<\/p>\n<p>OLM is composed of two main components:<\/p>\n<ul class=\"simple\">\n<li><strong>Catalogs<\/strong>: These are collections of Operators that can be\ninstalled on a Kubernetes cluster. Catalogs can be public or private,\nand can be hosted on container registries. Each Operator in a catalog\nhas a corresponding manifest that describes its deployment,\nconfiguration, and management. Catalogs allow users to easily\ndiscover, install and upgrade Operators on their cluster.<\/li>\n<li><strong>The Operator Lifecycle Manager<\/strong>: This is the control plane\ncomponent of OLM that manages the installation, upgrade, and removal\nof Operators on a Kubernetes cluster. The Operator Lifecycle Manager\nis responsible for ensuring that Operators are deployed and managed\naccording to their defined lifecycle. It monitors the status of\nOperators, handles upgrades and rollbacks, and ensures that\ndependencies between Operators are resolved correctly.<\/li>\n<\/ul>\n<p>OLM can be seen as a Linux Package Manager like <strong>DNF<\/strong>, indeed:<\/p>\n<ul class=\"simple\">\n<li>both rely on a manifest to ensure proper installation and\nconfiguration of the package (the operator).<\/li>\n<li>OLM and DNF package managers ensure that dependencies are resolved\nand the component is deployed and managed according to its defined\nlifecycle.<\/li>\n<li>both systems offer a standardized approach to managing software\ncomponents, improving system stability and efficiency.<\/li>\n<\/ul>\n<p>Here is a <a class=\"reference external\" href=\"https:\/\/olm.operatorframework.io\/docs\/glossary\/\">glossary<\/a> of the OLM terminology.<\/p>\n<\/div>\n<div class=\"section\" id=\"olm-installation\">\n<h2>OLM installation<\/h2>\n<p>The <a class=\"reference external\" href=\"https:\/\/sdk.operatorframework.io\/\">operator-sdk<\/a> tool provides a command to deploy OLM on a\nKubernetes deployment. This command creates various k8s resources to\nspawn OLM components and associated roles, role bindinds, service users,\n...<\/p>\n<div class=\"highlight\"><pre><span><\/span>operator-sdk<span class=\"w\"> <\/span>olm<span class=\"w\"> <\/span>install\n<\/pre><\/div>\n<p>Note that the Ansible role <a class=\"reference external\" href=\"https:\/\/github.com\/openstack-k8s-operators\/ansible-microshift-role\">ansible-microshift-role<\/a> provides an easy\nway to deploy a lightweight OpenShift environment (using <a class=\"reference external\" href=\"https:\/\/github.com\/openshift\/microshift\">Microshift<\/a>)\nwith OLM enabled.<\/p>\n<p>This command creates two namespaces:<\/p>\n<ul class=\"simple\">\n<li><strong>olm<\/strong>: It contains the OLM system with the <strong>catalog-operator<\/strong>,\n<strong>olm-operator<\/strong> and the <strong>packageserver<\/strong> deployments.<\/li>\n<li><strong>operators<\/strong>: It is the placeholder where one can subscribe to one\nor more operators. No resource is populated here by the OLM\ninstallation.<\/li>\n<\/ul>\n<div class=\"highlight\"><pre><span><\/span>kubectl<span class=\"w\"> <\/span>-n<span class=\"w\"> <\/span>olm<span class=\"w\"> <\/span>get<span class=\"w\"> <\/span>deployment\nNAME<span class=\"w\">               <\/span>READY<span class=\"w\">   <\/span>UP-TO-DATE<span class=\"w\">   <\/span>AVAILABLE<span class=\"w\">   <\/span>AGE\ncatalog-operator<span class=\"w\">   <\/span><span class=\"m\">1<\/span>\/1<span class=\"w\">     <\/span><span class=\"m\">1<\/span><span class=\"w\">            <\/span><span class=\"m\">1<\/span><span class=\"w\">           <\/span>17d\nolm-operator<span class=\"w\">       <\/span><span class=\"m\">1<\/span>\/1<span class=\"w\">     <\/span><span class=\"m\">1<\/span><span class=\"w\">            <\/span><span class=\"m\">1<\/span><span class=\"w\">           <\/span>17d\npackageserver<span class=\"w\">      <\/span><span class=\"m\">2<\/span>\/2<span class=\"w\">     <\/span><span class=\"m\">2<\/span><span class=\"w\">            <\/span><span class=\"m\">2<\/span><span class=\"w\">           <\/span>17d\n<\/pre><\/div>\n<p>Now we are able to extend our k8s instance by installing operators.<\/p>\n<\/div>\n<div class=\"section\" id=\"olm-usage\">\n<h2>OLM usage<\/h2>\n<p>In order to learn more about OLM, we will deploy the <a class=\"reference external\" href=\"https:\/\/operatorhub.io\/operator\/cert-manager\">cert-manager\noperator<\/a> from OLM.<\/p>\n<p>The OLM installation should come with the <strong>Community Operators<\/strong>\ncatalog installed:<\/p>\n<div class=\"highlight\"><pre><span><\/span>kubectl<span class=\"w\"> <\/span>-n<span class=\"w\"> <\/span>olm<span class=\"w\"> <\/span>get<span class=\"w\"> <\/span>catalogsources<span class=\"w\"> <\/span>operatorhubio-catalog\nNAME<span class=\"w\">                    <\/span>DISPLAY<span class=\"w\">               <\/span>TYPE<span class=\"w\">   <\/span>PUBLISHER<span class=\"w\">        <\/span>AGE\noperatorhubio-catalog<span class=\"w\">   <\/span>Community<span class=\"w\"> <\/span>Operators<span class=\"w\">   <\/span>grpc<span class=\"w\">   <\/span>OperatorHub.io<span class=\"w\">   <\/span>17d\n\nkubectl<span class=\"w\"> <\/span>-n<span class=\"w\"> <\/span>olm<span class=\"w\"> <\/span>get<span class=\"w\"> <\/span>-o<span class=\"w\"> <\/span>json<span class=\"w\"> <\/span>catalogsources<span class=\"w\"> <\/span>operatorhubio-catalog<span class=\"w\"> <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span>jq<span class=\"w\"> <\/span><span class=\"s1\">&#39;.spec&#39;<\/span>\n<span class=\"o\">{<\/span>\n<span class=\"w\">  <\/span><span class=\"s2\">&quot;displayName&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"s2\">&quot;Community Operators&quot;<\/span>,\n<span class=\"w\">  <\/span><span class=\"s2\">&quot;grpcPodConfig&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"o\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"s2\">&quot;securityContextConfig&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"s2\">&quot;restricted&quot;<\/span>\n<span class=\"w\">  <\/span><span class=\"o\">}<\/span>,\n<span class=\"w\">  <\/span><span class=\"s2\">&quot;image&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"s2\">&quot;quay.io\/operatorhubio\/catalog:latest&quot;<\/span>,\n<span class=\"w\">  <\/span><span class=\"s2\">&quot;publisher&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"s2\">&quot;OperatorHub.io&quot;<\/span>,\n<span class=\"w\">  <\/span><span class=\"s2\">&quot;sourceType&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"s2\">&quot;grpc&quot;<\/span>,\n<span class=\"w\">  <\/span><span class=\"s2\">&quot;updateStrategy&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"o\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"s2\">&quot;registryPoll&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"o\">{<\/span>\n<span class=\"w\">      <\/span><span class=\"s2\">&quot;interval&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"s2\">&quot;60m&quot;<\/span>\n<span class=\"w\">    <\/span><span class=\"o\">}<\/span>\n<span class=\"w\">  <\/span><span class=\"o\">}<\/span>\n<span class=\"o\">}<\/span>\n<\/pre><\/div>\n<p>Then we can explore the catalog for available operators:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"c1\"># There is more than 300 operators listed so let&#39;s grep for cert-manager<\/span>\nkubectl<span class=\"w\"> <\/span>-n<span class=\"w\"> <\/span>olm<span class=\"w\"> <\/span>get<span class=\"w\"> <\/span>packagemanifests<span class=\"w\"> <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span>grep<span class=\"w\"> <\/span>cert-manager\ncert-manager<span class=\"w\">                               <\/span>Community<span class=\"w\"> <\/span>Operators<span class=\"w\">   <\/span>17d\n<\/pre><\/div>\n<p>A <strong>PackageManifest<\/strong> resource describes the following:<\/p>\n<ul class=\"simple\">\n<li>The name and description of the package being managed.<\/li>\n<li>The package's installation process and any dependencies required.<\/li>\n<li>The default channel and available channels through which different\nversions of the package can be installed.<\/li>\n<li>A list of all versions of the package available through each channel.<\/li>\n<li>The latest version of the package available by channel\n(<tt class=\"docutils literal\">currentCSV<\/tt>).<\/li>\n<li>A list of CRDs that are installed along with the package.<\/li>\n<li>A list of global configuration variables for the package.<\/li>\n<\/ul>\n<p>The <strong>PackageManifest<\/strong> resource could be heavy to inspect, here are\nsome commands to help:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"c1\"># Show the package provider<\/span>\nkubectl<span class=\"w\"> <\/span>-n<span class=\"w\"> <\/span>olm<span class=\"w\"> <\/span>get<span class=\"w\"> <\/span>-o<span class=\"w\"> <\/span>json<span class=\"w\"> <\/span>packagemanifests<span class=\"w\"> <\/span>cert-manager<span class=\"w\"> <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span>jq<span class=\"w\"> <\/span><span class=\"s1\">&#39;.status.provider&#39;<\/span>\n<span class=\"o\">{<\/span>\n<span class=\"w\">  <\/span><span class=\"s2\">&quot;name&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"s2\">&quot;The cert-manager maintainers&quot;<\/span>,\n<span class=\"w\">  <\/span><span class=\"s2\">&quot;url&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"s2\">&quot;https:\/\/cert-manager.io\/&quot;<\/span>\n<span class=\"o\">}<\/span>\n\n<span class=\"c1\"># Show available channels for that package<\/span>\nkubectl<span class=\"w\"> <\/span>-n<span class=\"w\"> <\/span>olm<span class=\"w\"> <\/span>get<span class=\"w\"> <\/span>-o<span class=\"w\"> <\/span>json<span class=\"w\"> <\/span>packagemanifests<span class=\"w\"> <\/span>cert-manager<span class=\"w\"> <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span>jq<span class=\"w\"> <\/span><span class=\"s1\">&#39;.status.channels[].name&#39;<\/span>\n<span class=\"s2\">&quot;candidate&quot;<\/span>\n<span class=\"s2\">&quot;stable&quot;<\/span>\n\n<span class=\"c1\"># Show the default install channel of that package<\/span>\nkubectl<span class=\"w\"> <\/span>-n<span class=\"w\"> <\/span>olm<span class=\"w\"> <\/span>get<span class=\"w\"> <\/span>-o<span class=\"w\"> <\/span>json<span class=\"w\"> <\/span>packagemanifests<span class=\"w\"> <\/span>cert-manager<span class=\"w\"> <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span>jq<span class=\"w\"> <\/span><span class=\"s1\">&#39;.status.defaultChannel&#39;<\/span>\n<span class=\"s2\">&quot;stable&quot;<\/span>\n\n<span class=\"c1\"># Last version available (package head) in the stable channel<\/span>\nkubectl<span class=\"w\"> <\/span>-n<span class=\"w\"> <\/span>olm<span class=\"w\"> <\/span>get<span class=\"w\"> <\/span>-o<span class=\"w\"> <\/span>json<span class=\"w\"> <\/span>packagemanifests<span class=\"w\"> <\/span>cert-manager<span class=\"w\"> <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span>jq<span class=\"w\"> <\/span><span class=\"s1\">&#39;.status.channels[] | select(.name == &quot;stable&quot;) | .currentCSV&#39;<\/span>\n<span class=\"s2\">&quot;cert-manager.v1.11.0&quot;<\/span>\n\n<span class=\"c1\"># Versions from the stable channel<\/span>\nkubectl<span class=\"w\"> <\/span>-n<span class=\"w\"> <\/span>olm<span class=\"w\"> <\/span>get<span class=\"w\"> <\/span>-o<span class=\"w\"> <\/span>json<span class=\"w\"> <\/span>packagemanifests<span class=\"w\"> <\/span>cert-manager<span class=\"w\"> <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span>jq<span class=\"w\"> <\/span><span class=\"s1\">&#39;.status.channels[] | select(.name == &quot;stable&quot;) | .entries&#39;<\/span>\n<span class=\"o\">[<\/span>\n<span class=\"w\">  <\/span><span class=\"o\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"s2\">&quot;name&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"s2\">&quot;cert-manager.v1.11.0&quot;<\/span>,\n<span class=\"w\">    <\/span><span class=\"s2\">&quot;version&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"s2\">&quot;1.11.0&quot;<\/span>\n<span class=\"w\">  <\/span><span class=\"o\">}<\/span>,\n<span class=\"w\">  <\/span><span class=\"o\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"s2\">&quot;name&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"s2\">&quot;cert-manager.v1.10.2&quot;<\/span>,\n<span class=\"w\">    <\/span><span class=\"s2\">&quot;version&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"s2\">&quot;1.10.2&quot;<\/span>\n<span class=\"w\">  <\/span><span class=\"o\">}<\/span>,\n<span class=\"w\">  <\/span>...\n<span class=\"o\">]<\/span>\n\n<span class=\"c1\"># And finally, to show the CSV of the last stable version<\/span>\nkubectl<span class=\"w\"> <\/span>-n<span class=\"w\"> <\/span>olm<span class=\"w\"> <\/span>get<span class=\"w\"> <\/span>-o<span class=\"w\"> <\/span>json<span class=\"w\">  <\/span>packagemanifests<span class=\"w\"> <\/span>cert-manager<span class=\"w\"> <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span>jq<span class=\"w\"> <\/span><span class=\"s1\">&#39;.status.channels[] | select(.name == &quot;stable&quot;) | .currentCSVDesc&#39;<\/span>\n<\/pre><\/div>\n<p>The <strong>PackageManifest<\/strong> is built from a list of <a class=\"reference external\" href=\"https:\/\/docs.openshift.com\/container-platform\/4.12\/operators\/understanding\/olm-common-terms.html#olm-common-terms-csv_olm-common-terms\">ClusterServiceVersion\ndefinition<\/a>. The <strong>ClusterServiceVersion<\/strong> resource defines information\nthat is required to run the Operator, like the RBAC rules it requires\nand which custom resources (CRs) it manages or depends on.<\/p>\n<p>To install the <strong>cert-manager<\/strong> operator from the <strong>stable<\/strong> channel we\nneed to create a <a class=\"reference external\" href=\"https:\/\/olm.operatorframework.io\/docs\/concepts\/crds\/subscription\/\">Subscription<\/a>. It describes which channel of an\noperator package to subscribe to, and whether to perform updates\nautomatically or manually.<\/p>\n<p>Create the file <em>cert-manager.yaml<\/em>:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"nt\">apiVersion<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">operators.coreos.com\/v1alpha1<\/span>\n<span class=\"nt\">kind<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">Subscription<\/span>\n<span class=\"nt\">metadata<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">my-cert-manager<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">namespace<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">operators<\/span>\n<span class=\"nt\">spec<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">channel<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">stable<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">cert-manager<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">source<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">operatorhubio-catalog<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">sourceNamespace<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">olm<\/span>\n<span class=\"w\">  <\/span><span class=\"c1\"># By default is automatic upgrade plan<\/span>\n<span class=\"w\">  <\/span><span class=\"c1\"># installPlanApproval: Manual<\/span>\n<\/pre><\/div>\n<p>Then apply it with:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"c1\"># Apply the subscription<\/span>\nkubectl<span class=\"w\"> <\/span>apply<span class=\"w\"> <\/span>-f<span class=\"w\"> <\/span>cert-manager.yaml\n\n<span class=\"c1\"># Get the subscription<\/span>\nkubectl<span class=\"w\"> <\/span>-n<span class=\"w\"> <\/span>operators<span class=\"w\"> <\/span>get<span class=\"w\"> <\/span>sub\nNAME<span class=\"w\">                  <\/span>PACKAGE<span class=\"w\">            <\/span>SOURCE<span class=\"w\">                  <\/span>CHANNEL\nmy-cert-manager<span class=\"w\">       <\/span>cert-manager<span class=\"w\">       <\/span>operatorhubio-catalog<span class=\"w\">   <\/span>stable\n\n<span class=\"c1\"># Ensure the CSV is now available<\/span>\nkubectl<span class=\"w\"> <\/span>-n<span class=\"w\"> <\/span>operators<span class=\"w\"> <\/span>get<span class=\"w\"> <\/span>csv\nNAME<span class=\"w\">                       <\/span>DISPLAY<span class=\"w\">            <\/span>VERSION<span class=\"w\">   <\/span>REPLACES<span class=\"w\">                   <\/span>PHASE\ncert-manager.v1.11.0<span class=\"w\">       <\/span>cert-manager<span class=\"w\">       <\/span><span class=\"m\">1<\/span>.11.0<span class=\"w\">    <\/span>cert-manager.v1.10.2<span class=\"w\">       <\/span>Succeeded\n<\/pre><\/div>\n<p>Note that an <a class=\"reference external\" href=\"https:\/\/olm.operatorframework.io\/docs\/concepts\/crds\/installplan\/\">InstallPlan<\/a> resource has been created too. This is where\nyou can inspect installation steps on the operator. This resource could\nbe inspected in case the requested operator failed to be installed, for\ninstance when the <tt class=\"docutils literal\">csv<\/tt> resource has not been created.<\/p>\n<div class=\"highlight\"><pre><span><\/span>kubectl<span class=\"w\"> <\/span>-n<span class=\"w\"> <\/span>operators<span class=\"w\"> <\/span>describe<span class=\"w\"> <\/span>installplan<span class=\"w\"> <\/span>install-tkcrn\n<\/pre><\/div>\n<p>By default the <strong>Subscription<\/strong> set the <strong>installPlanApproval<\/strong> as\nautomatic. However if you decide to set it as manual, when OLM detects a\npossible upgrade (because of a new version available in the <tt class=\"docutils literal\">stable<\/tt>\nchannel), then the <tt class=\"docutils literal\">InstallPlan<\/tt> will need to be manually updated to\napprove the upgrade. The process is described <a class=\"reference external\" href=\"https:\/\/olm.operatorframework.io\/docs\/concepts\/crds\/subscription\/#manually-approving-upgrades-via-subscriptions\">here<\/a>.<\/p>\n<p>Beside the fact that the <tt class=\"docutils literal\"><span class=\"pre\">cert-manager.v1.11.0<\/span><\/tt> CSV phase is\n<tt class=\"docutils literal\">Succeeded<\/tt> we can verify that the <tt class=\"docutils literal\"><span class=\"pre\">cert-manager<\/span><\/tt> operator is\nrunning:<\/p>\n<div class=\"highlight\"><pre><span><\/span>kubectl<span class=\"w\"> <\/span>-n<span class=\"w\"> <\/span>operators<span class=\"w\"> <\/span>get<span class=\"w\"> <\/span>all<span class=\"w\"> <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span>grep<span class=\"w\"> <\/span>cert-manager\npod\/cert-manager-68c79ccf94-hkbp8<span class=\"w\">                               <\/span><span class=\"m\">1<\/span>\/1<span class=\"w\">     <\/span>Running<span class=\"w\">   <\/span><span class=\"m\">0<\/span><span class=\"w\">          <\/span>62m\npod\/cert-manager-cainjector-86c79dd959-q6x2q<span class=\"w\">                    <\/span><span class=\"m\">1<\/span>\/1<span class=\"w\">     <\/span>Running<span class=\"w\">   <\/span><span class=\"m\">0<\/span><span class=\"w\">          <\/span>62m\npod\/cert-manager-webhook-b685d8cd4-9q6jj<span class=\"w\">                        <\/span><span class=\"m\">1<\/span>\/1<span class=\"w\">     <\/span>Running<span class=\"w\">   <\/span><span class=\"m\">0<\/span><span class=\"w\">          <\/span>62m\nservice\/cert-manager<span class=\"w\">                                          <\/span>ClusterIP<span class=\"w\">   <\/span><span class=\"m\">10<\/span>.43.98.149<span class=\"w\">    <\/span>&lt;none&gt;<span class=\"w\">        <\/span><span class=\"m\">9402<\/span>\/TCP<span class=\"w\">   <\/span>63m\nservice\/cert-manager-webhook<span class=\"w\">                                  <\/span>ClusterIP<span class=\"w\">   <\/span><span class=\"m\">10<\/span>.43.18.198<span class=\"w\">    <\/span>&lt;none&gt;<span class=\"w\">        <\/span><span class=\"m\">443<\/span>\/TCP<span class=\"w\">    <\/span>63m\nservice\/cert-manager-webhook-service<span class=\"w\">                          <\/span>ClusterIP<span class=\"w\">   <\/span><span class=\"m\">10<\/span>.43.34.128<span class=\"w\">    <\/span>&lt;none&gt;<span class=\"w\">        <\/span><span class=\"m\">443<\/span>\/TCP<span class=\"w\">    <\/span>62m\ndeployment.apps\/cert-manager<span class=\"w\">                               <\/span><span class=\"m\">1<\/span>\/1<span class=\"w\">     <\/span><span class=\"m\">1<\/span><span class=\"w\">            <\/span><span class=\"m\">1<\/span><span class=\"w\">           <\/span>62m\ndeployment.apps\/cert-manager-cainjector<span class=\"w\">                    <\/span><span class=\"m\">1<\/span>\/1<span class=\"w\">     <\/span><span class=\"m\">1<\/span><span class=\"w\">            <\/span><span class=\"m\">1<\/span><span class=\"w\">           <\/span>62m\ndeployment.apps\/cert-manager-webhook<span class=\"w\">                       <\/span><span class=\"m\">1<\/span>\/1<span class=\"w\">     <\/span><span class=\"m\">1<\/span><span class=\"w\">            <\/span><span class=\"m\">1<\/span><span class=\"w\">           <\/span>62m\nreplicaset.apps\/cert-manager-68c79ccf94<span class=\"w\">                               <\/span><span class=\"m\">1<\/span><span class=\"w\">         <\/span><span class=\"m\">1<\/span><span class=\"w\">         <\/span><span class=\"m\">1<\/span><span class=\"w\">       <\/span>62m\nreplicaset.apps\/cert-manager-cainjector-86c79dd959<span class=\"w\">                    <\/span><span class=\"m\">1<\/span><span class=\"w\">         <\/span><span class=\"m\">1<\/span><span class=\"w\">         <\/span><span class=\"m\">1<\/span><span class=\"w\">       <\/span>62m\nreplicaset.apps\/cert-manager-webhook-b685d8cd4<span class=\"w\">                        <\/span><span class=\"m\">1<\/span><span class=\"w\">         <\/span><span class=\"m\">1<\/span><span class=\"w\">         <\/span><span class=\"m\">1<\/span><span class=\"w\">       <\/span>62m\n<\/pre><\/div>\n<p>The requested operator is installed in the same namespace as its\n<tt class=\"docutils literal\">Subscription<\/tt>.<\/p>\n<p>We can also ensure that the CRDs provided by the operator are available:<\/p>\n<div class=\"highlight\"><pre><span><\/span>kubectl<span class=\"w\"> <\/span>api-resources<span class=\"w\"> <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span>grep<span class=\"w\"> <\/span>cert-manager\nchallenges<span class=\"w\">                                     <\/span>acme.cert-manager.io\/v1<span class=\"w\">                      <\/span><span class=\"nb\">true<\/span><span class=\"w\">         <\/span>Challenge\norders<span class=\"w\">                                         <\/span>acme.cert-manager.io\/v1<span class=\"w\">                      <\/span><span class=\"nb\">true<\/span><span class=\"w\">         <\/span>Order\ncertificaterequests<span class=\"w\">               <\/span>cr,crs<span class=\"w\">       <\/span>cert-manager.io\/v1<span class=\"w\">                           <\/span><span class=\"nb\">true<\/span><span class=\"w\">         <\/span>CertificateRequest\ncertificates<span class=\"w\">                      <\/span>cert,certs<span class=\"w\">   <\/span>cert-manager.io\/v1<span class=\"w\">                           <\/span><span class=\"nb\">true<\/span><span class=\"w\">         <\/span>Certificate\nclusterissuers<span class=\"w\">                                 <\/span>cert-manager.io\/v1<span class=\"w\">                           <\/span><span class=\"nb\">false<\/span><span class=\"w\">        <\/span>ClusterIssuer\nissuers<span class=\"w\">                                        <\/span>cert-manager.io\/v1<span class=\"w\">                           <\/span><span class=\"nb\">true<\/span><span class=\"w\">         <\/span>Issuer\n<\/pre><\/div>\n<p>Finally, let's create a <tt class=\"docutils literal\">namespace<\/tt> and request an <tt class=\"docutils literal\">Issuer<\/tt> resource\nto the <tt class=\"docutils literal\"><span class=\"pre\">cert-manager<\/span> operator<\/tt>:<\/p>\n<p>Create the file <em>issuer.yaml<\/em>:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"nt\">apiVersion<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">cert-manager.io\/v1<\/span>\n<span class=\"nt\">kind<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">Issuer<\/span>\n<span class=\"nt\">metadata<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">example-issuer<\/span>\n<span class=\"nt\">spec<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">selfSigned<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p p-Indicator\">{}<\/span>\n<\/pre><\/div>\n<p>Then apply the resource in a new namespace:<\/p>\n<div class=\"highlight\"><pre><span><\/span>kubectl<span class=\"w\"> <\/span>ceate<span class=\"w\"> <\/span>ns<span class=\"w\"> <\/span>test-cert-manager\n\nkubectl<span class=\"w\"> <\/span>-n<span class=\"w\"> <\/span>test-cert-manager<span class=\"w\"> <\/span>apply<span class=\"w\"> <\/span>-f<span class=\"w\"> <\/span>issuer.yaml\nissuer.cert-manager.io\/example-issuer<span class=\"w\"> <\/span>created\n\nkubectl<span class=\"w\"> <\/span>-n<span class=\"w\"> <\/span>test-cert-manager<span class=\"w\"> <\/span>get<span class=\"w\"> <\/span>issuers\nNAME<span class=\"w\">             <\/span>READY<span class=\"w\">   <\/span>AGE\nexample-issuer<span class=\"w\">   <\/span>True<span class=\"w\">    <\/span>7s\n<\/pre><\/div>\n<\/div>\n<div class=\"section\" id=\"packaging-monocle-for-olm\">\n<h2>Packaging Monocle for OLM<\/h2>\n<p>Recently we wrote an <a class=\"reference external\" href=\"https:\/\/github.com\/change-metrics\/monocle-operator\">Operator<\/a> for the Monocle project and we were\ncurious about how to leverage OLM to make it easily consumable.<\/p>\n<p>An <a class=\"reference external\" href=\"https:\/\/github.com\/change-metrics\/monocle-operator\/tree\/6b8a02f9087f83798f732ede85cbe35c0304cb58\/install\">operator.yaml<\/a> file was generated by the\n<tt class=\"docutils literal\">kustomize build config\/default<\/tt> command, then it was possible to\napply the Monocle CRD and to <em>install<\/em> the required resources\n(namespace, serviceuser, roles, role bindings, deployments, ...) to get\nthe operator running.<\/p>\n<p>From there the process was to create the <a class=\"reference external\" href=\"https:\/\/olm.operatorframework.io\/docs\/glossary\/#bundle\">bundle<\/a> (or the package)\nusing the <tt class=\"docutils literal\">Makefile<\/tt>'s <tt class=\"docutils literal\">bundle<\/tt> target:<\/p>\n<div class=\"highlight\"><pre><span><\/span>make<span class=\"w\"> <\/span>bundle\n<\/pre><\/div>\n<p>This creates a directory called <strong>bundle<\/strong> which contains some\nsub-directories:<\/p>\n<ul class=\"simple\">\n<li><em>manifests<\/em>: containing mainly the CRD(s), and the\nClusterServiceVersion.<\/li>\n<li><em>metadata<\/em>: this is some annotations to describe the bundle.<\/li>\n<li><em>tests\/scorecard<\/em>: this describes various validation tests to be\nperformed on the bundle.<\/li>\n<\/ul>\n<p>Now we would like to <strong>validate our bundle<\/strong>, so we need to perform the\nfollowing steps.<\/p>\n<p>First we need to <strong>build and publish<\/strong> the <tt class=\"docutils literal\">bundle<\/tt>'s container image.\nTo do so, our <tt class=\"docutils literal\">Makefile<\/tt> provides the <tt class=\"docutils literal\"><span class=\"pre\">bundle-build<\/span><\/tt> and\n<tt class=\"docutils literal\"><span class=\"pre\">bundle-push<\/span><\/tt> targets:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"nb\">export<\/span><span class=\"w\"> <\/span><span class=\"nv\">BUNDLE_IMG<\/span><span class=\"o\">=<\/span>quay.io\/change-metrics\/monocle-operator-bundle:v0.0.1\nmake<span class=\"w\"> <\/span>bundle-build<span class=\"w\"> <\/span>bundle-push\n<\/pre><\/div>\n<p>Then we can use the <tt class=\"docutils literal\"><span class=\"pre\">operator-sdk<\/span> run bundle<\/tt> <a class=\"reference external\" href=\"https:\/\/sdk.operatorframework.io\/docs\/cli\/operator-sdk_run_bundle\/\">command<\/a> to <strong>validate\nthe bundle<\/strong>. The command drives these steps:<\/p>\n<ul class=\"simple\">\n<li>Create an <tt class=\"docutils literal\">operator catalog<\/tt> containing only our <tt class=\"docutils literal\">bundle<\/tt><\/li>\n<li>Run the <tt class=\"docutils literal\">registry<\/tt> pod to serve the new <tt class=\"docutils literal\">catalog<\/tt><\/li>\n<li>Create a <tt class=\"docutils literal\">CatalogSource<\/tt> resource to make the new <tt class=\"docutils literal\">catalog<\/tt>\navailable<\/li>\n<li>Create a <tt class=\"docutils literal\">Subscription<\/tt> and wait for the <tt class=\"docutils literal\">ClusterServiceVersion<\/tt>\nto be available.<\/li>\n<\/ul>\n<p>Note that this command needs to pull the bundle image from a real\ncontainer registry thus we run <tt class=\"docutils literal\"><span class=\"pre\">bundle-push<\/span><\/tt> to publish it. Running a\n<a class=\"reference external\" href=\"https:\/\/hub.docker.com\/_\/registry\">local registry<\/a> could ease that process by avoiding the need to push\nthe bundle image on <tt class=\"docutils literal\">dockerhub<\/tt> or <tt class=\"docutils literal\">quay.io<\/tt>.<\/p>\n<div class=\"highlight\"><pre><span><\/span>kubectl<span class=\"w\"> <\/span>create<span class=\"w\"> <\/span>ns<span class=\"w\"> <\/span>test-bundle\noc<span class=\"w\"> <\/span>adm<span class=\"w\"> <\/span>policy<span class=\"w\"> <\/span>add-scc-to-user<span class=\"w\"> <\/span>privileged<span class=\"w\"> <\/span>system:serviceaccount:test-bundle:default\n<span class=\"nb\">export<\/span><span class=\"w\"> <\/span><span class=\"nv\">BUNDLE_IMG<\/span><span class=\"o\">=<\/span>quay.io\/change-metrics\/monocle-operator-bundle:v0.0.1\noperator-sdk<span class=\"w\"> <\/span>run<span class=\"w\"> <\/span>bundle<span class=\"w\"> <\/span><span class=\"nv\">$BUNDLE_IMG<\/span><span class=\"w\"> <\/span>--namespace<span class=\"w\"> <\/span>test-bundle<span class=\"w\"> <\/span>--security-context-config<span class=\"w\"> <\/span>restricted\nINFO<span class=\"o\">[<\/span><span class=\"m\">0010<\/span><span class=\"o\">]<\/span><span class=\"w\"> <\/span>Creating<span class=\"w\"> <\/span>a<span class=\"w\"> <\/span>File-Based<span class=\"w\"> <\/span>Catalog<span class=\"w\"> <\/span>of<span class=\"w\"> <\/span>the<span class=\"w\"> <\/span>bundle<span class=\"w\"> <\/span><span class=\"s2\">&quot;quay.io\/change-metrics\/monocle-operator-bundle:v0.0.1&quot;<\/span>\nINFO<span class=\"o\">[<\/span><span class=\"m\">0011<\/span><span class=\"o\">]<\/span><span class=\"w\"> <\/span>Generated<span class=\"w\"> <\/span>a<span class=\"w\"> <\/span>valid<span class=\"w\"> <\/span>File-Based<span class=\"w\"> <\/span>Catalog\nINFO<span class=\"o\">[<\/span><span class=\"m\">0016<\/span><span class=\"o\">]<\/span><span class=\"w\"> <\/span>Created<span class=\"w\"> <\/span>registry<span class=\"w\"> <\/span>pod:<span class=\"w\"> <\/span>quay-io-change-metrics-monocle-operator-bundle-v0-0-1\nINFO<span class=\"o\">[<\/span><span class=\"m\">0016<\/span><span class=\"o\">]<\/span><span class=\"w\"> <\/span>Created<span class=\"w\"> <\/span>CatalogSource:<span class=\"w\"> <\/span>monocle-operator-catalog\nINFO<span class=\"o\">[<\/span><span class=\"m\">0016<\/span><span class=\"o\">]<\/span><span class=\"w\"> <\/span>OperatorGroup<span class=\"w\"> <\/span><span class=\"s2\">&quot;operator-sdk-og&quot;<\/span><span class=\"w\"> <\/span>created\nINFO<span class=\"o\">[<\/span><span class=\"m\">0016<\/span><span class=\"o\">]<\/span><span class=\"w\"> <\/span>Created<span class=\"w\"> <\/span>Subscription:<span class=\"w\"> <\/span>monocle-operator-v0-0-1-sub\nINFO<span class=\"o\">[<\/span><span class=\"m\">0022<\/span><span class=\"o\">]<\/span><span class=\"w\"> <\/span>Approved<span class=\"w\"> <\/span>InstallPlan<span class=\"w\"> <\/span>install-74dzl<span class=\"w\"> <\/span><span class=\"k\">for<\/span><span class=\"w\"> <\/span>the<span class=\"w\"> <\/span>Subscription:<span class=\"w\"> <\/span>monocle-operator-v0-0-1-sub\nINFO<span class=\"o\">[<\/span><span class=\"m\">0022<\/span><span class=\"o\">]<\/span><span class=\"w\"> <\/span>Waiting<span class=\"w\"> <\/span><span class=\"k\">for<\/span><span class=\"w\"> <\/span>ClusterServiceVersion<span class=\"w\"> <\/span><span class=\"s2\">&quot;test-bundle\/monocle-operator.v0.0.1&quot;<\/span><span class=\"w\"> <\/span>to<span class=\"w\"> <\/span>reach<span class=\"w\"> <\/span><span class=\"s1\">&#39;Succeeded&#39;<\/span><span class=\"w\"> <\/span>phase\nINFO<span class=\"o\">[<\/span><span class=\"m\">0022<\/span><span class=\"o\">]<\/span><span class=\"w\">   <\/span>Waiting<span class=\"w\"> <\/span><span class=\"k\">for<\/span><span class=\"w\"> <\/span>ClusterServiceVersion<span class=\"w\"> <\/span><span class=\"s2\">&quot;test-bundle\/monocle-operator.v0.0.1&quot;<\/span><span class=\"w\"> <\/span>to<span class=\"w\"> <\/span>appear\nINFO<span class=\"o\">[<\/span><span class=\"m\">0035<\/span><span class=\"o\">]<\/span><span class=\"w\">   <\/span>Found<span class=\"w\"> <\/span>ClusterServiceVersion<span class=\"w\"> <\/span><span class=\"s2\">&quot;test-bundle\/monocle-operator.v0.0.1&quot;<\/span><span class=\"w\"> <\/span>phase:<span class=\"w\"> <\/span>Pending\nINFO<span class=\"o\">[<\/span><span class=\"m\">0036<\/span><span class=\"o\">]<\/span><span class=\"w\">   <\/span>Found<span class=\"w\"> <\/span>ClusterServiceVersion<span class=\"w\"> <\/span><span class=\"s2\">&quot;test-bundle\/monocle-operator.v0.0.1&quot;<\/span><span class=\"w\"> <\/span>phase:<span class=\"w\"> <\/span>InstallReady\nINFO<span class=\"o\">[<\/span><span class=\"m\">0037<\/span><span class=\"o\">]<\/span><span class=\"w\">   <\/span>Found<span class=\"w\"> <\/span>ClusterServiceVersion<span class=\"w\"> <\/span><span class=\"s2\">&quot;test-bundle\/monocle-operator.v0.0.1&quot;<\/span><span class=\"w\"> <\/span>phase:<span class=\"w\"> <\/span>Installing\nINFO<span class=\"o\">[<\/span><span class=\"m\">0046<\/span><span class=\"o\">]<\/span><span class=\"w\">   <\/span>Found<span class=\"w\"> <\/span>ClusterServiceVersion<span class=\"w\"> <\/span><span class=\"s2\">&quot;test-bundle\/monocle-operator.v0.0.1&quot;<\/span><span class=\"w\"> <\/span>phase:<span class=\"w\"> <\/span>Succeeded\nINFO<span class=\"o\">[<\/span><span class=\"m\">0047<\/span><span class=\"o\">]<\/span><span class=\"w\"> <\/span>OLM<span class=\"w\"> <\/span>has<span class=\"w\"> <\/span>successfully<span class=\"w\"> <\/span>installed<span class=\"w\"> <\/span><span class=\"s2\">&quot;monocle-operator.v0.0.1&quot;<\/span>\n<\/pre><\/div>\n<p>The <tt class=\"docutils literal\"><span class=\"pre\">test-bundle<\/span><\/tt> namespace can be cleaned using:<\/p>\n<div class=\"highlight\"><pre><span><\/span>operator-sdk<span class=\"w\"> <\/span>cleanup<span class=\"w\"> <\/span>--namespace<span class=\"w\"> <\/span>test-bundle<span class=\"w\"> <\/span>monocle-operator\nINFO<span class=\"o\">[<\/span><span class=\"m\">0001<\/span><span class=\"o\">]<\/span><span class=\"w\"> <\/span>subscription<span class=\"w\"> <\/span><span class=\"s2\">&quot;monocle-operator-v0-0-1-sub&quot;<\/span><span class=\"w\"> <\/span>deleted\nINFO<span class=\"o\">[<\/span><span class=\"m\">0001<\/span><span class=\"o\">]<\/span><span class=\"w\"> <\/span>customresourcedefinition<span class=\"w\"> <\/span><span class=\"s2\">&quot;monocles.monocle.monocle.change-metrics.io&quot;<\/span><span class=\"w\"> <\/span>deleted\nINFO<span class=\"o\">[<\/span><span class=\"m\">0002<\/span><span class=\"o\">]<\/span><span class=\"w\"> <\/span>clusterserviceversion<span class=\"w\"> <\/span><span class=\"s2\">&quot;monocle-operator.v0.0.1&quot;<\/span><span class=\"w\"> <\/span>deleted\nINFO<span class=\"o\">[<\/span><span class=\"m\">0002<\/span><span class=\"o\">]<\/span><span class=\"w\"> <\/span>catalogsource<span class=\"w\"> <\/span><span class=\"s2\">&quot;monocle-operator-catalog&quot;<\/span><span class=\"w\"> <\/span>deleted\nINFO<span class=\"o\">[<\/span><span class=\"m\">0003<\/span><span class=\"o\">]<\/span><span class=\"w\"> <\/span>operatorgroup<span class=\"w\"> <\/span><span class=\"s2\">&quot;operator-sdk-og&quot;<\/span><span class=\"w\"> <\/span>deleted\nINFO<span class=\"o\">[<\/span><span class=\"m\">0003<\/span><span class=\"o\">]<\/span><span class=\"w\"> <\/span>Operator<span class=\"w\"> <\/span><span class=\"s2\">&quot;monocle-operator&quot;<\/span><span class=\"w\"> <\/span>uninstalled\n<\/pre><\/div>\n<p>At that point, we have a <em>validated<\/em> <tt class=\"docutils literal\">bundle<\/tt>. The next step is to\npublish\/distribute it. To do so, either:<\/p>\n<ul class=\"simple\">\n<li>we need to <a class=\"reference external\" href=\"https:\/\/sdk.operatorframework.io\/docs\/olm-integration\/tutorial-bundle\/#deploying-bundles-in-production\">maintain a catalog image<\/a>.<\/li>\n<li>or we distribute the bundle via an existing catalog like\n<a class=\"reference external\" href=\"https:\/\/operatorhub.io\">operatorhub.io<\/a>.<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"monocle-operator-on-operatorhub-io\">\n<span id=\"monocle-operator-on-operatorhubio\"><\/span><h2>Monocle operator on OperatorHub.io<\/h2>\n<p>We decided to propose the operator to the <strong>Community Catalog<\/strong>. This\nsection explains the process we followed to publish the Monocle Operator\non <a class=\"reference external\" href=\"https:\/\/operatorhub.io\">operatorhub.io<\/a>.<\/p>\n<p>First, we ensured that the <strong>required bundle CSV fields are present<\/strong>\n(see the <a class=\"reference external\" href=\"https:\/\/k8s-operatorhub.github.io\/community-operators\/packaging-required-fields\/\">required fields<\/a>). If not the CSV template needs to be\nadapted in\n<tt class=\"docutils literal\"><span class=\"pre\">config\/manifests\/bases\/monocle-operator.clusterserviceversion.yaml<\/span><\/tt>.<\/p>\n<p>The <tt class=\"docutils literal\">make bundle<\/tt> command must be run to apply changes to the\n<tt class=\"docutils literal\">bundle<\/tt> directory.<\/p>\n<p>We also <strong>validated the bundle<\/strong> with the <tt class=\"docutils literal\">validate<\/tt> command:<\/p>\n<div class=\"highlight\"><pre><span><\/span>operator-sdk<span class=\"w\"> <\/span>bundle<span class=\"w\"> <\/span>validate<span class=\"w\"> <\/span>.\/bundle<span class=\"w\"> <\/span>--select-optional<span class=\"w\"> <\/span><span class=\"nv\">suite<\/span><span class=\"o\">=<\/span>operatorframework\nINFO<span class=\"o\">[<\/span><span class=\"m\">0000<\/span><span class=\"o\">]<\/span><span class=\"w\"> <\/span>All<span class=\"w\"> <\/span>validation<span class=\"w\"> <\/span>tests<span class=\"w\"> <\/span>have<span class=\"w\"> <\/span>completed<span class=\"w\"> <\/span>successfully\n<\/pre><\/div>\n<p>Furthermore we <strong>run the scorecard validation<\/strong> (built-in basic and OLM\ntests. See <a class=\"reference external\" href=\"https:\/\/sdk.operatorframework.io\/docs\/testing-operators\/scorecard\/\">scorecard<\/a>):<\/p>\n<div class=\"highlight\"><pre><span><\/span>operator-sdk<span class=\"w\"> <\/span>scorecard<span class=\"w\"> <\/span>bundle<span class=\"w\"> <\/span>-o<span class=\"w\"> <\/span>text<span class=\"w\"> <\/span>--pod-security<span class=\"w\"> <\/span>restricted<span class=\"w\"> <\/span>-n<span class=\"w\"> <\/span>scorecard\n<\/pre><\/div>\n<p>Finally we created a <a class=\"reference external\" href=\"https:\/\/github.com\/k8s-operatorhub\/community-operators\/pull\/2668\">Pull Request<\/a> on the\n<a class=\"reference external\" href=\"https:\/\/github.com\/k8s-operatorhub\/community-operators\">k8s-operatorhub\/community-operators<\/a> repository.<\/p>\n<p>This Pull Request includes a copy of the <tt class=\"docutils literal\">bundle<\/tt> located in a new\ndirectory called <tt class=\"docutils literal\"><span class=\"pre\">operators\/monocle-operator\/0.0.1<\/span><\/tt>. The\n<tt class=\"docutils literal\"><span class=\"pre\">operators\/monocle-operator\/ci.yaml<\/span><\/tt> file was also needed to define\n<a class=\"reference external\" href=\"https:\/\/k8s-operatorhub.github.io\/community-operators\/operator-ci-yaml\/#operator-versioning\">various settings<\/a> for the operatorhub.io's CI pipelines.<\/p>\n<p>After some back and forth, mainly thanks to the operatorhub.io's CI\ncatching issues, the Monocle Operator Pull Request landed and few\nminutes later (propably the time required by the CD pipeline to update\nand publish the catalog) it <a class=\"reference external\" href=\"https:\/\/operatorhub.io\/operator\/monocle-operator\">appeared on the operatorhub.io website<\/a>,\nand was available on our Microshift installation:<\/p>\n<div class=\"highlight\"><pre><span><\/span>kubectl<span class=\"w\"> <\/span>-n<span class=\"w\"> <\/span>olm<span class=\"w\"> <\/span>get<span class=\"w\"> <\/span>packagemanifests<span class=\"w\"> <\/span>monocle-operator\nNAME<span class=\"w\">               <\/span>CATALOG<span class=\"w\">               <\/span>AGE\nmonocle-operator<span class=\"w\">   <\/span>Community<span class=\"w\"> <\/span>Operators<span class=\"w\">   <\/span>18d\n<\/pre><\/div>\n<p>Feel free to refer to the upstream <a class=\"reference external\" href=\"https:\/\/k8s-operatorhub.github.io\/community-operators\/\">Add your operator - documentation<\/a>\nfor more details.<\/p>\n<\/div>\n<div class=\"section\" id=\"to-conclude\">\n<h2>To conclude<\/h2>\n<p>As we are working closer with OpenShift and the Go Operator pattern, our\nteam decided to investigate OLM to gather knowledge. After some readings\nand experimentations we were able to figure out how to leverage OLM to\ndistribute a Kubernetes operator. We used the Monocle Operator to\nperform that experimentation because it was almost <em>ready to bundle<\/em>.\nThis experimentation will help us to better align our further\ndevelopments for <strong>SF 4.X<\/strong> aka the <a class=\"reference external\" href=\"https:\/\/github.com\/softwarefactory-project\/sf-operator\">sf-operator<\/a>.<\/p>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2023 Apr 14 to May 03 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2023-apr-14-to-may-03-summary.html","rel":"alternate"}},"published":"2023-05-03T10:00:00+00:00","updated":"2023-05-03T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2023-05-03:\/sprint-2023-apr-14-to-may-03-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We now use stream9-minimal containers for all sf services and all containers are built with microdnf without weak dependencies<\/li>\n<li>We fixed the SecurityContextConstraint violations triggered by running in restricted namespace<\/li>\n<li>We reported an issue to quay where \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We now use stream9-minimal containers for all sf services and all containers are built with microdnf without weak dependencies<\/li>\n<li>We fixed the SecurityContextConstraint violations triggered by running in restricted namespace<\/li>\n<li>We reported an issue to quay where image can become a zombie<\/li>\n<li>We fixed the rbac issues triggered by running the operator in a deployment<\/li>\n<li>We are working on having a next 3.8.9 release of SF 3.8<\/li>\n<li>We defined the SF4 alpha-1 milestone<\/li>\n<li>We did some Some refactoring on the post.yaml (artifacts fetching) <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28122\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28122<\/a><\/li>\n<li>We investigated why our container was not working as random user by openshift<\/li>\n<li>We enabled OLM for sf-operator test node via ansible-microshift-role <a class=\"reference external\" href=\"https:\/\/github.com\/openstack-k8s-operators\/ansible-microshift-role\/pull\/20\">https:\/\/github.com\/openstack-k8s-operators\/ansible-microshift-role\/pull\/20<\/a><\/li>\n<li>We are installing the cert-manager via OLM <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28216\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28216<\/a><\/li>\n<li>We enabled the vanilla installation the operator and updated the RBAC <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28251\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28251<\/a><\/li>\n<li>Adding Purgelogs Service to Software Factory Operator - <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28111\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28111<\/a><\/li>\n<li>LogServer Controller - <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28288\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/28288<\/a><\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2023 Mar 24 to Apr 12 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2023-mar-24-to-apr-12-summary.html","rel":"alternate"}},"published":"2023-04-12T10:00:00+00:00","updated":"2023-04-12T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2023-04-12:\/sprint-2023-mar-24-to-apr-12-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We fixed few issues related to the logsender, where some files were empty or subunit files were incorrect and the files were still available on the server.<\/li>\n<li>We upgraded Opensearch on Opendev to version 2.5 (from 1 \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We fixed few issues related to the logsender, where some files were empty or subunit files were incorrect and the files were still available on the server.<\/li>\n<li>We upgraded Opensearch on Opendev to version 2.5 (from 1.1 to 1.3 then 1.3 to 2.5)<\/li>\n<li>If all would be fine with logscraper and logsender changes, we will do a release version for ci-log-processing o\/<\/li>\n<li>We proposed a change to upload-logs to better support log servers that implement rrsync <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul-jobs\/+\/878829\">https:\/\/review.opendev.org\/c\/zuul\/zuul-jobs\/+\/878829<\/a> but no love (so we modified the scp container to remove rrsync)<\/li>\n<li>Merged support for username\/password as arguments in zuul-client if the OIDC provider supports direct grant access, negating the need to fetch a JWT by your own means<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>we now use stream9-minimal for all containers except zookeeper and logserver we have to migrate<\/li>\n<li>we bumped zuul and nodepool to version 8.2.0, and 3.6.4 for gerrit<\/li>\n<li>we added functional tests to validate zuul-web api and from user <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/27984\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/27984<\/a><\/li>\n<li>We added cache dir volume for MySQL service due the temporary file were stored in delta dir in podman storage overlay, so the servers were from time to time out of the disk space<\/li>\n<li>We added PodSecurityContext and SecurityContext to avoid warning message on running sf-operator. It should also unlock partially using Microshift 4.13<\/li>\n<li>We fixed an issue related to the Zuul console log was not working correctly<\/li>\n<li>We've added a playbook to streamline microshift deployment in sf-operator's toolbox<\/li>\n<li>We validated that volume expansion is available out of the box with microshift<\/li>\n<li>We are adding storageSize properties to sf-operator services instead of using default sizes. Next step will be to react to CR changes for these properties to expand volumes.<\/li>\n<li>We investigated how to handle PVC deletion<\/li>\n<li>We added the support for the config-update (resources and zuul config apply)<\/li>\n<li>We improved CI job duration and fixed various flakiness<\/li>\n<li>We fixed the Zuul Gerrit comment to link to the logserver <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/27921\/\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/27921\/<\/a><\/li>\n<li>We improved the README and CONTRIBUTING doc and added a section about how to deploy Microshift <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/27958\/\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/27958\/<\/a><\/li>\n<li>We added a CI task to fetch artifacts produced by zuul jobs during the sf-operator functional test <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/27977\/\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/27977\/<\/a><\/li>\n<li>We worked on defining the next milestone for sf-4.0<\/li>\n<li>We are working on maintainance release sf-config 3.8.9 including some fixes discovered after the upgrade<\/li>\n<li>We added Logserver and tests with official OpenShift Container to SFO<\/li>\n<li>We are adding PurgeLogs and test to SFO<\/li>\n<li>We fixed Zuul Web UI broken Gerrit UI links<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2023 Mar 02 to Mar 22 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2023-mar-02-to-mar-22-summary.html","rel":"alternate"}},"published":"2023-03-22T10:00:00+00:00","updated":"2023-03-22T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2023-03-22:\/sprint-2023-mar-02-to-mar-22-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We scheduled the Opensearch upgrade date<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We removed all components not included in the mvp from sf-operator<\/li>\n<li>We created script to allow dev to run ci-jobs from laptop to reproduce zuul workflow and modify roles to \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We scheduled the Opensearch upgrade date<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We removed all components not included in the mvp from sf-operator<\/li>\n<li>We created script to allow dev to run ci-jobs from laptop to reproduce zuul workflow and modify roles to work in both envs<\/li>\n<li>We are working to add simple tests to clone, modify and submit change on the config repo, then check results in zuul-web (ported from sf-ci)<\/li>\n<li>We started working on the logserver operator. This requires an rsync container that we need to develop.<\/li>\n<li>We improved the CI flakyness by ensuring the zuul-scheduler container is running before doing the tenant configuration.<\/li>\n<li>We replaced the kubernetes Ingress with openshift Route: <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/27808\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/27808<\/a><\/li>\n<li>We worked on improving developer experience (like by the use of topolvm: this use dynamic PV provisionning and remove the need to hack on the microshift node to clear PVs, removing needs for MY_NS var as we all use a dedicated microshift instance, ...)<\/li>\n<li>We added a fix for sf-config that is not including the SELinux labels on mounting the volume for the purgelogs because the service restart takes ages and from time to time it just &quot;freeze&quot; probably because of missing label.<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Monocle Operator - Phase 1 - Basic Install","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/monocle-operator-phase-1-basic-install.html","rel":"alternate"}},"published":"2023-03-10T00:00:00+00:00","updated":"2023-03-10T00:00:00+00:00","author":{"name":"Fabien Boucher and Fransisco De Seruca Salgado"},"id":"tag:www.softwarefactory-project.io,2023-03-10:\/monocle-operator-phase-1-basic-install.html","summary":"<style type=\"text\/css\">\n\n  .literal {\n    border-radius: 6px;\n    padding: 1px 1px;\n    background-color: rgba(27,31,35,.05);\n  }\n\n<\/style><p>This post aims to explain how we built a k8s Operator using the\n<a class=\"reference external\" href=\"https:\/\/sdk.operatorframework.io\/\">operator SDK<\/a> for the <a class=\"reference external\" href=\"https:\/\/changemetrics.io\">Monocle<\/a> project.<\/p>\n<p>We used the opportunity of <tt class=\"docutils literal\">Monocle<\/tt>, which is a quite simple project\nin terms of architecture and workflows, to \u2026<\/p>","content":"<style type=\"text\/css\">\n\n  .literal {\n    border-radius: 6px;\n    padding: 1px 1px;\n    background-color: rgba(27,31,35,.05);\n  }\n\n<\/style><p>This post aims to explain how we built a k8s Operator using the\n<a class=\"reference external\" href=\"https:\/\/sdk.operatorframework.io\/\">operator SDK<\/a> for the <a class=\"reference external\" href=\"https:\/\/changemetrics.io\">Monocle<\/a> project.<\/p>\n<p>We used the opportunity of <tt class=\"docutils literal\">Monocle<\/tt>, which is a quite simple project\nin terms of architecture and workflows, to experiment with the k8s's\n<a class=\"reference external\" href=\"https:\/\/kubernetes.io\/docs\/concepts\/extend-kubernetes\/operator\/\">operator pattern<\/a> and the <tt class=\"docutils literal\">operator SDK<\/tt>.<\/p>\n<p>We started that work based on limited knowledges about the k8s and\nespecially the Operator pattern, so this blog post also serves the\npurpose of sharing our understanding from a novice perspective.<\/p>\n<p>We'll cover the following topics:<\/p>\n<ul class=\"simple\">\n<li>What is a k8s Operator<\/li>\n<li>How to create the project skeleton<\/li>\n<li>Workflows related to the Monocle's operations<\/li>\n<li>Handling Monocle' workflows with the operator<\/li>\n<li>How to generate and deploy the operator<\/li>\n<\/ul>\n<p>You can find the <tt class=\"docutils literal\"><span class=\"pre\">monocle-operator<\/span><\/tt> on this <a class=\"reference external\" href=\"https:\/\/github.com\/change-metrics\/monocle-operator\/tree\/813eb65df2da2249a5f2f0dd348ac4a3b6f11f0c\">github project<\/a>.<\/p>\n<div class=\"section\" id=\"what-is-a-k8s-operator-1\">\n<span id=\"what-is-a-k8s-operator\"><\/span><h2>What is a k8s Operator ?<\/h2>\n<p>An <a class=\"reference external\" href=\"https:\/\/kubernetes.io\/docs\/concepts\/extend-kubernetes\/operator\/\">Operator<\/a> is a software capable of handling various operations\nrelated to another software. The Operator handles operations usually\nensured by a SRE.<\/p>\n<p>Handled operations are such as (but not limited to):<\/p>\n<ul class=\"simple\">\n<li>Deployment<\/li>\n<li>(Re-)Configuration<\/li>\n<li>Update<\/li>\n<li>Scaling<\/li>\n<li>Backup<\/li>\n<\/ul>\n<p>Capabilities of an operator are split into <a class=\"reference external\" href=\"https:\/\/operatorframework.io\/operator-capabilities\/\">phases<\/a> as described in the\nOperator framework documentation.<\/p>\n<p>To create an Operator a developer needs to well understand how to\noperate the target software.<\/p>\n<p>A multitude of Operators for various softwares are already available\nespecially on <a class=\"reference external\" href=\"https:\/\/operatorhub.io\">Operator Hub<\/a> and <a class=\"reference external\" href=\"https:\/\/catalog.redhat.com\/software\/search?deployed_as=Operator\">Red Hat Ecosystem Catalog<\/a><\/p>\n<p>An Operator is designed to live inside a k8s or OpenShift cluster. The\noperator uses k8s' resources (Deployment, ConfigMap, ...) to handle the\ntarget software' operations. It manages at least one CR (<a class=\"reference external\" href=\"https:\/\/kubernetes.io\/docs\/concepts\/extend-kubernetes\/api-extension\/custom-resources\/\">Custom\nResource<\/a>) that is defined by a CRD (<a class=\"reference external\" href=\"https:\/\/kubernetes.io\/docs\/concepts\/extend-kubernetes\/api-extension\/custom-resources\/#customresourcedefinitions\">Custom Resource Definition<\/a>) and\ncontrolled by a <a class=\"reference external\" href=\"https:\/\/kubernetes.io\/docs\/concepts\/extend-kubernetes\/api-extension\/custom-resources\/#custom-controllers\">Custom Controller<\/a>.<\/p>\n<p>A Custom Controller watches for CR instances and ensures that k8s'\nresources needed by the CR' instances are spawned and fully functional.\nThe controller runs continuously and reacts to various events ensuring\nthe declared state of the software is maintained. This is called\n&quot;Reconciliation&quot;.<\/p>\n<p>In this blog post we introduce an Operator for the Monocle software,\nbased on a Custom Resource and a Custom controller. The controller is\nimplemented in Go to benefit from well tested and documented libraries.<\/p>\n<\/div>\n<div class=\"section\" id=\"how-to-create-the-project-skeleton-1\">\n<span id=\"how-to-create-the-project-skeleton\"><\/span><h2>How to create the project skeleton ?<\/h2>\n<p>The <a class=\"reference external\" href=\"https:\/\/operatorframework.io\/\">Operator framework project<\/a> provides a <a class=\"reference external\" href=\"https:\/\/sdk.operatorframework.io\/\">SDK<\/a> to ease the\nbootstrap and maintainance of an operator.<\/p>\n<p>First install the <a class=\"reference external\" href=\"https:\/\/sdk.operatorframework.io\/docs\/installation\/\">GO operator SDK<\/a>:<\/p>\n<div class=\"highlight\"><pre><span><\/span>curl<span class=\"w\"> <\/span>-OL<span class=\"w\"> <\/span>https:\/\/github.com\/operator-framework\/operator-sdk\/releases\/download\/v1.26.0\/operator-sdk_linux_amd64\nmkdir<span class=\"w\"> <\/span>-p<span class=\"w\"> <\/span>~\/.local\/bin<span class=\"w\"> <\/span><span class=\"o\">&amp;&amp;<\/span><span class=\"w\"> <\/span>mv<span class=\"w\"> <\/span>operator-sdk_linux_amd64<span class=\"w\"> <\/span>~\/.local\/bin\/operator-sdk<span class=\"w\"> <\/span><span class=\"o\">&amp;&amp;<\/span><span class=\"w\"> <\/span>chmod<span class=\"w\"> <\/span>+x<span class=\"w\"> <\/span>~\/.local\/bin\/operator-sdk\n<\/pre><\/div>\n<p><a class=\"reference external\" href=\"https:\/\/sdk.operatorframework.io\/docs\/cli\/operator-sdk_init\/\">Initialise your repository<\/a> using the <tt class=\"docutils literal\">init<\/tt> sub command:<\/p>\n<div class=\"highlight\"><pre><span><\/span>mkdir<span class=\"w\"> <\/span>monocle-operator<span class=\"w\"> <\/span><span class=\"o\">&amp;&amp;<\/span><span class=\"w\"> <\/span><span class=\"nb\">cd<\/span><span class=\"w\"> <\/span>monocle-operator\ngit<span class=\"w\"> <\/span>init<span class=\"w\"> <\/span>.\noperator-sdk<span class=\"w\"> <\/span>init<span class=\"w\"> <\/span>--repo<span class=\"w\"> <\/span>github.com\/change-metrics\/monocle-operator<span class=\"w\"> <\/span>--owner<span class=\"w\"> <\/span><span class=\"s2\">&quot;Monocle developers&quot;<\/span><span class=\"w\"> <\/span>--domain<span class=\"w\"> <\/span>monocle.change-metrics.io\ngit<span class=\"w\"> <\/span>diff\ngit<span class=\"w\"> <\/span>add<span class=\"w\"> <\/span>-A<span class=\"w\"> <\/span>.<span class=\"w\"> <\/span><span class=\"o\">&amp;&amp;<\/span><span class=\"w\"> <\/span>git<span class=\"w\"> <\/span>commit<span class=\"w\"> <\/span>-m<span class=\"s2\">&quot;Init the operator&quot;<\/span>\n<\/pre><\/div>\n<p>Then, <a class=\"reference external\" href=\"https:\/\/sdk.operatorframework.io\/docs\/cli\/operator-sdk_create_api\/\">add the new API<\/a> (a CRD) and a controller for the new\n<tt class=\"docutils literal\">Monocle<\/tt> Custom Resource:<\/p>\n<div class=\"highlight\"><pre><span><\/span>operator-sdk<span class=\"w\"> <\/span>create<span class=\"w\"> <\/span>api<span class=\"w\"> <\/span>--group<span class=\"w\"> <\/span>monocle<span class=\"w\"> <\/span>--version<span class=\"w\"> <\/span>v1alpha1<span class=\"w\"> <\/span>--kind<span class=\"w\"> <\/span>Monocle<span class=\"w\"> <\/span>--resource<span class=\"w\"> <\/span>--controller\ngit<span class=\"w\"> <\/span>status\ngit<span class=\"w\"> <\/span>diff\ngit<span class=\"w\"> <\/span>add<span class=\"w\"> <\/span>-A<span class=\"w\"> <\/span>.<span class=\"w\"> <\/span><span class=\"o\">&amp;&amp;<\/span><span class=\"w\"> <\/span>git<span class=\"w\"> <\/span>commit<span class=\"w\"> <\/span>-m<span class=\"s2\">&quot;Add skeleton code for the Monocle CR&quot;<\/span>\n<\/pre><\/div>\n<p>If the Operator handles more that one CR then run the previous command\nwith the new <tt class=\"docutils literal\">Kind<\/tt>.<\/p>\n<p>The SDK for a <a class=\"reference external\" href=\"https:\/\/sdk.operatorframework.io\/docs\/building-operators\/golang\/quickstart\/\">GO operator<\/a> generates the project code structure\ncomposed of various files and directories. Check the <a class=\"reference external\" href=\"https:\/\/master.sdk.operatorframework.io\/docs\/overview\/project-layout\/\">layout details\nhere<\/a>.<\/p>\n<p>We can see that an Operator is, at least defined, by the following\nresources:<\/p>\n<ul class=\"simple\">\n<li>A <a class=\"reference external\" href=\"https:\/\/pkg.go.dev\/sigs.k8s.io\/controller-runtime#hdr-Managers\">manager<\/a> and a set of <a class=\"reference external\" href=\"https:\/\/pkg.go.dev\/sigs.k8s.io\/controller-runtime#hdr-Controllers\">controllers<\/a><\/li>\n<li>A set of <a class=\"reference external\" href=\"https:\/\/kubernetes.io\/docs\/concepts\/extend-kubernetes\/api-extension\/custom-resources\/#customresourcedefinitions\">CRDs<\/a><\/li>\n<li>A container image capable of running the <tt class=\"docutils literal\">manager<\/tt><\/li>\n<li>A suite of YAML manifests to apply to the Kubernetes cluster to\ndeploy the operator<\/li>\n<\/ul>\n<p>From there we are ready to write the Monocle Operator.<\/p>\n<\/div>\n<div class=\"section\" id=\"workflows-related-to-the-monocle-s-operations\">\n<h2>Workflows related to the Monocle's operations<\/h2>\n<p>An operator handles various workflows for the targeted software. Thus,\nas a first step we need to identify exactly what are those workflows and\nwhat they involve.<\/p>\n<p>For our <tt class=\"docutils literal\">Phase 1<\/tt> journey we'd like to handle the deployment and the\nconfiguration of Monocle. It is important to have a minimum\nunderstanding of the software we intent to create an operator for. Feel\nfree to read the <a class=\"reference external\" href=\"https:\/\/github.com\/change-metrics\/monocle#readme\">Monocle's README file<\/a>.<\/p>\n<div class=\"section\" id=\"deployment\">\n<h3>Deployment<\/h3>\n<p>A minimal Monocle deployment is composed of three services. The upstream\nproject provides a <a class=\"reference external\" href=\"https:\/\/github.com\/change-metrics\/monocle\/blob\/master\/docker-compose.yml\">Docker Compose file<\/a> that we will replicate.<\/p>\n<div class=\"section\" id=\"the-database-elasticsearch\">\n<h4>The database (ElasticSearch)<\/h4>\n<p>Monocle needs to get access to an ElasticSearch instance:<\/p>\n<ul class=\"simple\">\n<li>The service needs a storage for its indices.<\/li>\n<li>We can use the upstream ElasticSearch container image.<\/li>\n<li>We can rely on the minimal and default settings.<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"the-monocle-api-serve-the-api-and-the-web-ui\">\n<h4>The Monocle API (serve the API and the WEB UI)<\/h4>\n<ul class=\"simple\">\n<li>The upstream project provides a container image.<\/li>\n<li>The service is stateless.<\/li>\n<li>The service connects to the database.<\/li>\n<li>A configuration file is needed.<\/li>\n<li>Some environment variables must be exposed (especially for the\nsecrets).<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"the-monocle-crawler\">\n<h4>The Monocle crawler<\/h4>\n<p>The crawler requires the same as the API, except that the service\nconnects to the API service (not to the database).<\/p>\n<\/div>\n<\/div>\n<div class=\"section\" id=\"configuration\">\n<h3>Configuration<\/h3>\n<p>Here we need to determine how an User will interact with the Monocle\nOperator in order to change the Monocle configuration.<\/p>\n<div class=\"section\" id=\"update-secrets\">\n<h4>Update secrets<\/h4>\n<p>The <a class=\"reference external\" href=\"https:\/\/github.com\/change-metrics\/monocle#environment-variables\">secrets<\/a> hosts sensitive information used by the API and the\ncrawler processes (Code Review provider's API tokens, OpenID Token,\n...). Any changes to the <tt class=\"docutils literal\">secrets<\/tt> require an API and crawler\nprocesses restart.<\/p>\n<\/div>\n<div class=\"section\" id=\"update-config-yaml\">\n<span id=\"update-configyaml\"><\/span><h4>Update config.yaml<\/h4>\n<p>The <a class=\"reference external\" href=\"https:\/\/github.com\/change-metrics\/monocle#configuration-file\">config file<\/a> is used by the API and the crawler. Monocle is able\nto detect changes in its configuration file and apply configuration\nupdates automatically.<\/p>\n<p>The <tt class=\"docutils literal\">janitor <span class=\"pre\">update-idents<\/span><\/tt> command must be run in case of updating\nthe <tt class=\"docutils literal\">config file<\/tt> to <a class=\"reference external\" href=\"https:\/\/github.com\/change-metrics\/monocle#apply-idents-configuration\">update identities<\/a>.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"section\" id=\"handling-monocle-s-workflows-with-the-operator\">\n<h2>Handling Monocle's workflows with the Operator<\/h2>\n<p>As we know better about workflows we need to implement inside our\nMonocle controller we can start to implement it. We'll just explain some\ncode blocks.<\/p>\n<p>Feel free to refer to the <a class=\"reference external\" href=\"https:\/\/github.com\/change-metrics\/monocle-operator\/blob\/813eb65df2da2249a5f2f0dd348ac4a3b6f11f0c\/controllers\/monocle_controller.go\">complete controller code<\/a>.<\/p>\n<div class=\"section\" id=\"the-reconcile-loop\">\n<h3>The reconcile loop<\/h3>\n<p>The operator SDK generated an empty Monocle's <tt class=\"docutils literal\">Reconcile<\/tt> function.<\/p>\n<p>This function aims to make the requested state (by applying the\n<tt class=\"docutils literal\">Monocle<\/tt> resource) to be the state into the cluster. When a\n<tt class=\"docutils literal\">Monocle<\/tt> resource is applied to the cluster we want to provide a\nworking Monocle deployment with the database, the api, and the crawler.<\/p>\n<p>Furthermore various attributes can be configured into the <tt class=\"docutils literal\">spec<\/tt> (see\n<tt class=\"docutils literal\">api\/v1alpha1\/monocle_types.go<\/tt><a class=\"reference external\" href=\"https:\/\/github.com\/change-metrics\/monocle-operator\/blob\/813eb65df2da2249a5f2f0dd348ac4a3b6f11f0c\/api\/v1alpha1\/monocle_types.go\">monocle-types<\/a>) via the CRD so we\nneed to get the instance's <tt class=\"docutils literal\">spec<\/tt> to gather all information about the\nexpected state.<\/p>\n<p>The Monocle CRD is autogenerated from the <a class=\"reference external\" href=\"https:\/\/github.com\/change-metrics\/monocle-operator\/blob\/813eb65df2da2249a5f2f0dd348ac4a3b6f11f0c\/api\/v1alpha1\/monocle_types.go\">monocle go types<\/a> by the SDK\n(<tt class=\"docutils literal\">make manifests<\/tt>). Here you can see the <a class=\"reference external\" href=\"https:\/\/github.com\/change-metrics\/monocle-operator\/blob\/813eb65df2da2249a5f2f0dd348ac4a3b6f11f0c\/config\/crd\/bases\/monocle.monocle.change-metrics.io_monocles.yaml\">Monocle CRD<\/a>. For Monocle,\nwe added only one field into the <tt class=\"docutils literal\">spec<\/tt> to set up the\n<tt class=\"docutils literal\">monoclePublicURL<\/tt>. Any changes to the CRD must be done via the Go\ntypes defintion.<\/p>\n<p>To do so we write the function in order to get the Monocle instance\nResource according to the <a class=\"reference external\" href=\"https:\/\/pkg.go.dev\/sigs.k8s.io\/controller-runtime&#64;v0.14.5\/pkg\/reconcile#Request\">req<\/a> content:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"kd\">func<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"nx\">r<\/span><span class=\"w\"> <\/span><span class=\"o\">*<\/span><span class=\"nx\">MonocleReconciler<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"nx\">Reconcile<\/span><span class=\"p\">(<\/span><span class=\"nx\">ctx<\/span><span class=\"w\"> <\/span><span class=\"nx\">context<\/span><span class=\"p\">.<\/span><span class=\"nx\">Context<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">req<\/span><span class=\"w\"> <\/span><span class=\"nx\">ctrl<\/span><span class=\"p\">.<\/span><span class=\"nx\">Request<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"nx\">ctrl<\/span><span class=\"p\">.<\/span><span class=\"nx\">Result<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"kt\">error<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n\n<span class=\"w\">   <\/span><span class=\"kd\">var<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">logger<\/span><span class=\"w\">         <\/span><span class=\"p\">=<\/span><span class=\"w\"> <\/span><span class=\"nx\">log<\/span><span class=\"p\">.<\/span><span class=\"nx\">FromContext<\/span><span class=\"p\">(<\/span><span class=\"nx\">ctx<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">reconcileLater<\/span><span class=\"w\"> <\/span><span class=\"p\">=<\/span><span class=\"w\"> <\/span><span class=\"kd\">func<\/span><span class=\"p\">(<\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"kt\">error<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span>\n<span class=\"w\">            <\/span><span class=\"nx\">ctrl<\/span><span class=\"p\">.<\/span><span class=\"nx\">Result<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"kt\">error<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">            <\/span><span class=\"k\">return<\/span><span class=\"w\"> <\/span><span class=\"nx\">ctrl<\/span><span class=\"p\">.<\/span><span class=\"nx\">Result<\/span><span class=\"p\">{<\/span><span class=\"nx\">RequeueAfter<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">time<\/span><span class=\"p\">.<\/span><span class=\"nx\">Second<\/span><span class=\"w\"> <\/span><span class=\"o\">*<\/span><span class=\"w\"> <\/span><span class=\"mi\">5<\/span><span class=\"p\">},<\/span><span class=\"w\"> <\/span><span class=\"nx\">err<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">stopReconcile<\/span><span class=\"w\"> <\/span><span class=\"p\">=<\/span><span class=\"w\"> <\/span><span class=\"kd\">func<\/span><span class=\"p\">()<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span>\n<span class=\"w\">            <\/span><span class=\"nx\">ctrl<\/span><span class=\"p\">.<\/span><span class=\"nx\">Result<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"kt\">error<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">            <\/span><span class=\"k\">return<\/span><span class=\"w\"> <\/span><span class=\"nx\">ctrl<\/span><span class=\"p\">.<\/span><span class=\"nx\">Result<\/span><span class=\"p\">{},<\/span><span class=\"w\"> <\/span><span class=\"kc\">nil<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">instance<\/span><span class=\"w\"> <\/span><span class=\"p\">=<\/span><span class=\"w\"> <\/span><span class=\"nx\">monoclev1alpha1<\/span><span class=\"p\">.<\/span><span class=\"nx\">Monocle<\/span><span class=\"p\">{}<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">)<\/span>\n\n<span class=\"w\">   <\/span><span class=\"c1\">\/\/ Get the Monocle instance related to request<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"nx\">r<\/span><span class=\"p\">.<\/span><span class=\"nx\">Client<\/span><span class=\"p\">.<\/span><span class=\"nx\">Get<\/span><span class=\"p\">(<\/span><span class=\"nx\">ctx<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">req<\/span><span class=\"p\">.<\/span><span class=\"nx\">NamespacedName<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">instance<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"o\">!=<\/span><span class=\"w\"> <\/span><span class=\"kc\">nil<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"nx\">k8s_errors<\/span><span class=\"p\">.<\/span><span class=\"nx\">IsNotFound<\/span><span class=\"p\">(<\/span><span class=\"nx\">err<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">            <\/span><span class=\"c1\">\/\/ Request object not found. Return and don&#39;t requeue.<\/span>\n<span class=\"w\">            <\/span><span class=\"nx\">logger<\/span><span class=\"p\">.<\/span><span class=\"nx\">Info<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Instance object not found. Stop reconcile.&quot;<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">            <\/span><span class=\"c1\">\/\/ Stop reconcile<\/span>\n<span class=\"w\">            <\/span><span class=\"k\">return<\/span><span class=\"w\"> <\/span><span class=\"nx\">stopReconcile<\/span><span class=\"p\">()<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">        <\/span><span class=\"c1\">\/\/ Error reading the object - requeue the request.<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">logger<\/span><span class=\"p\">.<\/span><span class=\"nx\">Info<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Unable to read the Monocle object. Reconcile continues ...&quot;<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">        <\/span><span class=\"c1\">\/\/ Stop reconcile<\/span>\n<span class=\"w\">        <\/span><span class=\"k\">return<\/span><span class=\"w\"> <\/span><span class=\"nx\">reconcileLater<\/span><span class=\"p\">(<\/span><span class=\"nx\">err<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n\n<span class=\"w\">   <\/span><span class=\"nx\">logger<\/span><span class=\"p\">.<\/span><span class=\"nx\">Info<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Found Monocle object.&quot;<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">return<\/span><span class=\"w\"> <\/span><span class=\"nx\">stopReconcile<\/span><span class=\"p\">()<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<p>This <tt class=\"docutils literal\">Reconcile<\/tt> function is called every time an event occurs on a\nMonocle instance such as by an apply or an update:<\/p>\n<div class=\"highlight\"><pre><span><\/span>$<span class=\"w\"> <\/span>kubectl<span class=\"w\"> <\/span>apply<span class=\"w\"> <\/span>-f<span class=\"w\"> <\/span>config\/samples\/monocle_v1alpha1_monocle.yaml\n$<span class=\"w\"> <\/span>kubectl<span class=\"w\"> <\/span>edit<span class=\"w\"> <\/span>Monocle<span class=\"w\"> <\/span>monocle-sample\n<\/pre><\/div>\n<p>The <tt class=\"docutils literal\"><span class=\"pre\">operator-sdk<\/span> create api<\/tt> created a default\n<tt class=\"docutils literal\">config\/samples\/monocle_v1alpha1_monocle.yaml<\/tt> file that we can use to\nreclaim an instance of <tt class=\"docutils literal\">Monocle<\/tt>.<\/p>\n<p>Based on that minimal <tt class=\"docutils literal\">Reconcile<\/tt> function implementation we can\nexperiment:<\/p>\n<p>Start the manager in dev mode:<\/p>\n<div class=\"highlight\"><pre><span><\/span>$<span class=\"w\"> <\/span>make<span class=\"w\"> <\/span>run\n<span class=\"c1\"># or<\/span>\n$<span class=\"w\"> <\/span>go<span class=\"w\"> <\/span>run<span class=\"w\"> <\/span>.\/main.yaml\n<\/pre><\/div>\n<p>In another terminal you can <tt class=\"docutils literal\">apply<\/tt> the resource with:<\/p>\n<div class=\"highlight\"><pre><span><\/span>$<span class=\"w\"> <\/span>kubectl<span class=\"w\"> <\/span>apply<span class=\"w\"> <\/span>-f<span class=\"w\"> <\/span>config\/samples\/monocle_v1alpha1_monocle.yaml\n<\/pre><\/div>\n<p>Then the <tt class=\"docutils literal\">Monocle's controller<\/tt> should display and stop the reconcile\nloop:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"m\">1<\/span>.6781911388888087e+09<span class=\"w\">  <\/span>INFO<span class=\"w\">    <\/span>controller-runtime.metrics<span class=\"w\">      <\/span>Metrics<span class=\"w\"> <\/span>server<span class=\"w\"> <\/span>is<span class=\"w\"> <\/span>starting<span class=\"w\"> <\/span>to<span class=\"w\"> <\/span>listen<span class=\"w\">    <\/span><span class=\"o\">{<\/span><span class=\"s2\">&quot;addr&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"s2\">&quot;:8080&quot;<\/span><span class=\"o\">}<\/span>\n...\n<span class=\"m\">1<\/span>.6781911390910478e+09<span class=\"w\">  <\/span>INFO<span class=\"w\">    <\/span>Starting<span class=\"w\"> <\/span>workers<span class=\"w\">        <\/span><span class=\"o\">{<\/span><span class=\"s2\">&quot;controller&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"s2\">&quot;monocle&quot;<\/span>,<span class=\"w\"> <\/span><span class=\"s2\">&quot;controllerGroup&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"s2\">&quot;monocle.monocle.change-metrics.io&quot;<\/span>,<span class=\"w\"> <\/span><span class=\"s2\">&quot;controllerKind&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"s2\">&quot;Monocle&quot;<\/span>,<span class=\"w\"> <\/span><span class=\"s2\">&quot;worker count&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"m\">1<\/span><span class=\"o\">}<\/span>\n<span class=\"m\">1<\/span>.6781911505580697e+09<span class=\"w\">  <\/span>INFO<span class=\"w\">    <\/span>Found<span class=\"w\"> <\/span>Monocle<span class=\"w\"> <\/span>object.<span class=\"w\">   <\/span><span class=\"o\">{<\/span><span class=\"s2\">&quot;controller&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"s2\">&quot;monocle&quot;<\/span>,<span class=\"w\"> <\/span><span class=\"s2\">&quot;controllerGroup&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"s2\">&quot;monocle.monocle.change-metrics.io&quot;<\/span>,<span class=\"w\"> <\/span><span class=\"s2\">&quot;controllerKind&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"s2\">&quot;Monocle&quot;<\/span>,<span class=\"w\"> <\/span><span class=\"s2\">&quot;Monocle&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"o\">{<\/span><span class=\"s2\">&quot;name&quot;<\/span>:<span class=\"s2\">&quot;monocle-sample&quot;<\/span>,<span class=\"s2\">&quot;namespace&quot;<\/span>:<span class=\"s2\">&quot;fbo&quot;<\/span><span class=\"o\">}<\/span>,<span class=\"w\"> <\/span><span class=\"s2\">&quot;namespace&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"s2\">&quot;fbo&quot;<\/span>,<span class=\"w\"> <\/span><span class=\"s2\">&quot;name&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"s2\">&quot;monocle-sample&quot;<\/span>,<span class=\"w\"> <\/span><span class=\"s2\">&quot;reconcileID&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"s2\">&quot;580d1b93-e4d8-41ef-8996-817e198727ff&quot;<\/span><span class=\"o\">}<\/span>\n<\/pre><\/div>\n<p>You can observe that the <tt class=\"docutils literal\">controller<\/tt> re-enters the reconcile loop\nwhen we edit the Monocle instance:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"c1\"># Add a new label in metadata.labels and save.<\/span>\n$<span class=\"w\"> <\/span>kubectl<span class=\"w\"> <\/span>edit<span class=\"w\"> <\/span>monocle<span class=\"w\"> <\/span>monocle-sample\n<\/pre><\/div>\n<p>The return value of the reconcile function controls how the\n<tt class=\"docutils literal\">controller<\/tt> re-enter it. See <a class=\"reference external\" href=\"https:\/\/pkg.go.dev\/sigs.k8s.io\/controller-runtime&#64;v0.14.5\/pkg\/reconcile#Reconciler\">details here<\/a>.<\/p>\n<p>Next steps are to handle the deployment of the services that compose a\nMonocle deployment.<\/p>\n<\/div>\n<div class=\"section\" id=\"how-the-operator-starts-monocle-components\">\n<h3>How the operator starts Monocle' components<\/h3>\n<p>We'll only focus on the <tt class=\"docutils literal\">api<\/tt> service in that section. Other services\nare pretty similar except the database service that is deployed via the\n<a class=\"reference external\" href=\"https:\/\/kubernetes.io\/docs\/concepts\/workloads\/controllers\/statefulset\/\">StatefulSet<\/a>.<\/p>\n<p>Feel free to refer to the <a class=\"reference external\" href=\"https:\/\/github.com\/change-metrics\/monocle-operator\/blob\/813eb65df2da2249a5f2f0dd348ac4a3b6f11f0c\/controllers\/monocle_controller.go\">complete controller code<\/a>.<\/p>\n<div class=\"section\" id=\"the-api-secret\">\n<h4>The API secret<\/h4>\n<p>The <tt class=\"docutils literal\">Monocle<\/tt> API service needs to access some secrets data. Here we\nuse the <a class=\"reference external\" href=\"https:\/\/kubernetes.io\/docs\/concepts\/configuration\/secret\/\">Secret<\/a> resource to store this data.<\/p>\n<p>The Monocle's controller needs to:<\/p>\n<ul class=\"simple\">\n<li>Check if the secret exist<\/li>\n<li>Create the secret resource if it does not exist<\/li>\n<li>Continue if it exists<\/li>\n<\/ul>\n<p>The <tt class=\"docutils literal\">secret<\/tt> is identified by its name and as a good practice\nResource's names must be unique in a single <tt class=\"docutils literal\">namespace<\/tt>.<\/p>\n<p>Here is how we handle the <tt class=\"docutils literal\">secret<\/tt> resource (<a class=\"reference external\" href=\"https:\/\/pkg.go.dev\/k8s.io\/api\/core\/v1#Secret\">type<\/a>):<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"c1\">\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/<\/span>\n<span class=\"c1\">\/\/       Handle the Monocle API Secret Instance       \/\/<\/span>\n<span class=\"c1\">\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/<\/span>\n\n<span class=\"c1\">\/\/ This secret contains environment variables required by the<\/span>\n<span class=\"c1\">\/\/ API and\/or crawlers. The CRAWLERS_API_KEY entry is<\/span>\n<span class=\"c1\">\/\/ mandatory for crawlers to authenticate against the API.<\/span>\n\n<span class=\"c1\">\/\/ preprend the resource name with the instance name<\/span>\n<span class=\"nx\">apiSecretName<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"nx\">resourceName<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;api&quot;<\/span><span class=\"p\">)<\/span>\n<span class=\"c1\">\/\/ initialize a mapping with a random crawler&#39;s api key<\/span>\n<span class=\"nx\">apiSecretData<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"kd\">map<\/span><span class=\"p\">[<\/span><span class=\"kt\">string<\/span><span class=\"p\">][]<\/span><span class=\"kt\">byte<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"s\">&quot;CRAWLERS_API_KEY&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">[]<\/span><span class=\"nb\">byte<\/span><span class=\"p\">(<\/span><span class=\"nx\">randstr<\/span><span class=\"p\">.<\/span><span class=\"nx\">String<\/span><span class=\"p\">(<\/span><span class=\"mi\">24<\/span><span class=\"p\">))}<\/span>\n<span class=\"c1\">\/\/ create the secret instance with required metadata for the lookup<\/span>\n<span class=\"nx\">apiSecret<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"nx\">corev1<\/span><span class=\"p\">.<\/span><span class=\"nx\">Secret<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">ObjectMeta<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">metav1<\/span><span class=\"p\">.<\/span><span class=\"nx\">ObjectMeta<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">Name<\/span><span class=\"p\">:<\/span><span class=\"w\">      <\/span><span class=\"nx\">apiSecretName<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">Namespace<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">req<\/span><span class=\"p\">.<\/span><span class=\"nx\">Namespace<\/span><span class=\"p\">},<\/span>\n<span class=\"p\">}<\/span>\n<span class=\"c1\">\/\/ get the secret resource by name<\/span>\n<span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"p\">=<\/span><span class=\"w\"> <\/span><span class=\"nx\">r<\/span><span class=\"p\">.<\/span><span class=\"nx\">Client<\/span><span class=\"p\">.<\/span><span class=\"nx\">Get<\/span><span class=\"p\">(<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">ctx<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">client<\/span><span class=\"p\">.<\/span><span class=\"nx\">ObjectKey<\/span><span class=\"p\">{<\/span><span class=\"nx\">Name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiSecretName<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">Namespace<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">req<\/span><span class=\"p\">.<\/span><span class=\"nx\">Namespace<\/span><span class=\"p\">},<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">apiSecret<\/span><span class=\"p\">)<\/span>\n<span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"o\">!=<\/span><span class=\"w\"> <\/span><span class=\"kc\">nil<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;&amp;<\/span><span class=\"w\"> <\/span><span class=\"nx\">k8s_errors<\/span><span class=\"p\">.<\/span><span class=\"nx\">IsNotFound<\/span><span class=\"p\">(<\/span><span class=\"nx\">err<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">   <\/span><span class=\"c1\">\/\/ The resource does not exist yet. Let&#39;s create it.<\/span>\n<span class=\"w\">   <\/span><span class=\"c1\">\/\/ Set secret data<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">apiSecret<\/span><span class=\"p\">.<\/span><span class=\"nx\">Data<\/span><span class=\"w\"> <\/span><span class=\"p\">=<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiSecretData<\/span>\n<span class=\"w\">   <\/span><span class=\"c1\">\/\/ Add an owner reference (Monocle instance) on the secret resource<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"nx\">ctrl_util<\/span><span class=\"p\">.<\/span><span class=\"nx\">SetControllerReference<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">instance<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">apiSecret<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">r<\/span><span class=\"p\">.<\/span><span class=\"nx\">Scheme<\/span><span class=\"p\">);<\/span><span class=\"w\"> <\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"o\">!=<\/span><span class=\"w\"> <\/span><span class=\"kc\">nil<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">logger<\/span><span class=\"p\">.<\/span><span class=\"nx\">Info<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Unable to set controller reference&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;name&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiSecretName<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">        <\/span><span class=\"k\">return<\/span><span class=\"w\"> <\/span><span class=\"nx\">reconcileLater<\/span><span class=\"p\">(<\/span><span class=\"nx\">err<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ Create the secret<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">logger<\/span><span class=\"p\">.<\/span><span class=\"nx\">Info<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Creating secret&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;name&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiSecretName<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"nx\">r<\/span><span class=\"p\">.<\/span><span class=\"nx\">Create<\/span><span class=\"p\">(<\/span><span class=\"nx\">ctx<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">apiSecret<\/span><span class=\"p\">);<\/span><span class=\"w\"> <\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"o\">!=<\/span><span class=\"w\"> <\/span><span class=\"kc\">nil<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">logger<\/span><span class=\"p\">.<\/span><span class=\"nx\">Info<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Unable to create secret&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;name&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiSecretName<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">        <\/span><span class=\"k\">return<\/span><span class=\"w\"> <\/span><span class=\"nx\">reconcileLater<\/span><span class=\"p\">(<\/span><span class=\"nx\">err<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span><span class=\"w\"> <\/span><span class=\"k\">else<\/span><span class=\"w\"> <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"o\">!=<\/span><span class=\"w\"> <\/span><span class=\"kc\">nil<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ Handle the unexpected err<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">logger<\/span><span class=\"p\">.<\/span><span class=\"nx\">Info<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Unable to get resource&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;name&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiSecretName<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">return<\/span><span class=\"w\"> <\/span><span class=\"nx\">reconcileLater<\/span><span class=\"p\">(<\/span><span class=\"nx\">err<\/span><span class=\"p\">)<\/span>\n<span class=\"p\">}<\/span><span class=\"w\"> <\/span><span class=\"k\">else<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ Eventually handle resource update<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">logger<\/span><span class=\"p\">.<\/span><span class=\"nx\">Info<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Resource fetched successfuly&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;name&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiSecretName<\/span><span class=\"p\">)<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"c1\">\/\/ Get the resource version - to be used later ...<\/span>\n<span class=\"nx\">apiSecretsVersion<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiSecret<\/span><span class=\"p\">.<\/span><span class=\"nx\">ResourceVersion<\/span>\n<span class=\"nx\">logger<\/span><span class=\"p\">.<\/span><span class=\"nx\">Info<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;apiSecret resource&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;version&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiSecretsVersion<\/span><span class=\"p\">)<\/span>\n<\/pre><\/div>\n<p>As you can see, we check for the secret state and perform actions\naccording to the state. We use the <a class=\"reference external\" href=\"https:\/\/pkg.go.dev\/sigs.k8s.io\/controller-runtime\/pkg\/client\">Client<\/a> exposed through the\n<tt class=\"docutils literal\">MonocleReconciler<\/tt> interface to perform CRUD actions.<\/p>\n<p>This is a common pattern that we'll use for other resources managed by\nthe controller.<\/p>\n<\/div>\n<div class=\"section\" id=\"the-api-config\">\n<h4>The API config<\/h4>\n<p>The\n<a class=\"reference external\" href=\"https:\/\/kubernetes.io\/docs\/concepts\/configuration\/configmap\/\">ConfigMap<\/a>(<a class=\"reference external\" href=\"https:\/\/pkg.go.dev\/k8s.io\/api\/core\/v1#ConfigMap\">type<\/a>)\nare pretty similar regarding their API so the implementation is\nequivalent as for the <tt class=\"docutils literal\">secret<\/tt>.<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"c1\">\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/<\/span>\n<span class=\"c1\">\/\/     Handle the Monocle API ConfigMap Instance      \/\/<\/span>\n<span class=\"c1\">\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/<\/span>\n\n<span class=\"c1\">\/\/ preprend the resource name with the instance name<\/span>\n<span class=\"nx\">apiConfigMapName<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"nx\">resourceName<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;api&quot;<\/span><span class=\"p\">)<\/span>\n<span class=\"c1\">\/\/ initialize a mapping with the default config file<\/span>\n<span class=\"nx\">apiConfigMapData<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"kd\">map<\/span><span class=\"p\">[<\/span><span class=\"kt\">string<\/span><span class=\"p\">]<\/span><span class=\"kt\">string<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"s\">&quot;config.yaml&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s\">`<\/span>\n<span class=\"s\">workspaces:<\/span>\n<span class=\"s\">  - name: demo<\/span>\n<span class=\"s\">    crawlers: []<\/span>\n<span class=\"s\">`<\/span><span class=\"p\">}<\/span>\n<span class=\"c1\">\/\/ create the config-map instance with required metadata for the lookup<\/span>\n<span class=\"nx\">apiConfigMap<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"nx\">corev1<\/span><span class=\"p\">.<\/span><span class=\"nx\">ConfigMap<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">ObjectMeta<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">metav1<\/span><span class=\"p\">.<\/span><span class=\"nx\">ObjectMeta<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">Name<\/span><span class=\"p\">:<\/span><span class=\"w\">      <\/span><span class=\"nx\">apiConfigMapName<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">Namespace<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">req<\/span><span class=\"p\">.<\/span><span class=\"nx\">Namespace<\/span><span class=\"p\">},<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"c1\">\/\/ get the configmap resource by name<\/span>\n<span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"p\">=<\/span><span class=\"w\"> <\/span><span class=\"nx\">r<\/span><span class=\"p\">.<\/span><span class=\"nx\">Client<\/span><span class=\"p\">.<\/span><span class=\"nx\">Get<\/span><span class=\"p\">(<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">ctx<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">client<\/span><span class=\"p\">.<\/span><span class=\"nx\">ObjectKey<\/span><span class=\"p\">{<\/span><span class=\"nx\">Name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiConfigMapName<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">Namespace<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">req<\/span><span class=\"p\">.<\/span><span class=\"nx\">Namespace<\/span><span class=\"p\">},<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">apiConfigMap<\/span><span class=\"p\">)<\/span>\n<span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"o\">!=<\/span><span class=\"w\"> <\/span><span class=\"kc\">nil<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;&amp;<\/span><span class=\"w\"> <\/span><span class=\"nx\">k8s_errors<\/span><span class=\"p\">.<\/span><span class=\"nx\">IsNotFound<\/span><span class=\"p\">(<\/span><span class=\"nx\">err<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">   <\/span><span class=\"c1\">\/\/ The resource does not exist yet. Let&#39;s create it.<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">apiConfigMap<\/span><span class=\"p\">.<\/span><span class=\"nx\">Data<\/span><span class=\"w\"> <\/span><span class=\"p\">=<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiConfigMapData<\/span>\n<span class=\"w\">   <\/span><span class=\"c1\">\/\/ Add an owner reference (Monocle instance) on the configmap resource<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"nx\">ctrl_util<\/span><span class=\"p\">.<\/span><span class=\"nx\">SetControllerReference<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">instance<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">apiConfigMap<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">r<\/span><span class=\"p\">.<\/span><span class=\"nx\">Scheme<\/span><span class=\"p\">);<\/span><span class=\"w\"> <\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"o\">!=<\/span><span class=\"w\"> <\/span><span class=\"kc\">nil<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">logger<\/span><span class=\"p\">.<\/span><span class=\"nx\">Info<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Unable to set controller reference&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;name&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiConfigMapName<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">        <\/span><span class=\"k\">return<\/span><span class=\"w\"> <\/span><span class=\"nx\">reconcileLater<\/span><span class=\"p\">(<\/span><span class=\"nx\">err<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ Create the configMap<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">logger<\/span><span class=\"p\">.<\/span><span class=\"nx\">Info<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Creating ConfigMap&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;name&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiConfigMapName<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"nx\">r<\/span><span class=\"p\">.<\/span><span class=\"nx\">Create<\/span><span class=\"p\">(<\/span><span class=\"nx\">ctx<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">apiConfigMap<\/span><span class=\"p\">);<\/span><span class=\"w\"> <\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"o\">!=<\/span><span class=\"w\"> <\/span><span class=\"kc\">nil<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">logger<\/span><span class=\"p\">.<\/span><span class=\"nx\">Info<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Unable to create configMap&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;name&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiConfigMap<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">        <\/span><span class=\"k\">return<\/span><span class=\"w\"> <\/span><span class=\"nx\">reconcileLater<\/span><span class=\"p\">(<\/span><span class=\"nx\">err<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span><span class=\"w\"> <\/span><span class=\"k\">else<\/span><span class=\"w\"> <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"o\">!=<\/span><span class=\"w\"> <\/span><span class=\"kc\">nil<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ Handle the unexpected err<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">logger<\/span><span class=\"p\">.<\/span><span class=\"nx\">Info<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Unable to get resource&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;name&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiConfigMapName<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">return<\/span><span class=\"w\"> <\/span><span class=\"nx\">reconcileLater<\/span><span class=\"p\">(<\/span><span class=\"nx\">err<\/span><span class=\"p\">)<\/span>\n<span class=\"p\">}<\/span><span class=\"w\"> <\/span><span class=\"k\">else<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ Eventually handle resource update<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">logger<\/span><span class=\"p\">.<\/span><span class=\"nx\">Info<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Resource fetched successfuly&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;name&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiConfigMapName<\/span><span class=\"p\">)<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"c1\">\/\/ Get the resource version - to be used later ...<\/span>\n<span class=\"nx\">apiConfigVersion<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiConfigMap<\/span><span class=\"p\">.<\/span><span class=\"nx\">ResourceVersion<\/span>\n<span class=\"nx\">logger<\/span><span class=\"p\">.<\/span><span class=\"nx\">Info<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;apiConfig resource&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;version&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiConfigVersion<\/span><span class=\"p\">)<\/span>\n<\/pre><\/div>\n<p>For all resources created by the Monocle <tt class=\"docutils literal\">controller<\/tt> we set an\n<a class=\"reference external\" href=\"https:\/\/kubernetes.io\/docs\/concepts\/overview\/working-with-objects\/owners-dependents\/\">OwnerReference<\/a>. This ensures that when we delete the CR instance then\nall dependents resources are also deleted. It serves also to the\n<tt class=\"docutils literal\">manager<\/tt> to call the reconcile function when a dependent resource is\nupdated.<\/p>\n<\/div>\n<div class=\"section\" id=\"the-api-deployment\">\n<h4>The API deployment<\/h4>\n<p>To run the API service we use the <a class=\"reference external\" href=\"https:\/\/kubernetes.io\/docs\/concepts\/workloads\/controllers\/deployment\/\">Deployment\nresource<\/a>(<a class=\"reference external\" href=\"https:\/\/pkg.go.dev\/k8s.io\/api&#64;v0.26.2\/apps\/v1#Deployment\">type<\/a>)\nand in front of it we configure a\n<a class=\"reference external\" href=\"https:\/\/kubernetes.io\/docs\/concepts\/services-networking\/service\/\">Service<\/a>(<a class=\"reference external\" href=\"https:\/\/pkg.go.dev\/k8s.io\/api\/core\/v1#Service\">type<\/a>)\nresource.<\/p>\n<p>A <tt class=\"docutils literal\">Deployment<\/tt> manages a set of <tt class=\"docutils literal\">Pods<\/tt> according to rules and\nworkflows implemented in the <tt class=\"docutils literal\">Deployment<\/tt>'s controller.<\/p>\n<p><tt class=\"docutils literal\">Pods<\/tt> can be spawned on different cluster's nodes, in which the node\nwill assign an IP address to each container within a pod. To tackle this\ndynamic address assiging <tt class=\"docutils literal\">Service<\/tt> resource is needed on top of a\n<tt class=\"docutils literal\">Deployment<\/tt>.<\/p>\n<p>Let's start by creating the <tt class=\"docutils literal\"><span class=\"pre\">api-service<\/span><\/tt> resource:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"c1\">\/\/ Handle service for api \/\/<\/span>\n<span class=\"c1\">\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/<\/span>\n\n<span class=\"c1\">\/\/ The monocle API listen to 8080\/TCP<\/span>\n<span class=\"nx\">apiPort<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"mi\">8080<\/span>\n<span class=\"c1\">\/\/ MatchLabels shared between the service and the deployment<\/span>\n<span class=\"nx\">apiMatchLabels<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"kd\">map<\/span><span class=\"p\">[<\/span><span class=\"kt\">string<\/span><span class=\"p\">]<\/span><span class=\"kt\">string<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"s\">&quot;app&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\">  <\/span><span class=\"s\">&quot;monocle&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">    <\/span><span class=\"s\">&quot;tier&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;api&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"p\">}<\/span>\n<span class=\"c1\">\/\/ Service resource name<\/span>\n<span class=\"nx\">apiServiceName<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"nx\">resourceName<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;api&quot;<\/span><span class=\"p\">)<\/span>\n<span class=\"c1\">\/\/ Instanciate a Service object for the lookup<\/span>\n<span class=\"nx\">apiService<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"nx\">corev1<\/span><span class=\"p\">.<\/span><span class=\"nx\">Service<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">ObjectMeta<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">metav1<\/span><span class=\"p\">.<\/span><span class=\"nx\">ObjectMeta<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">Name<\/span><span class=\"p\">:<\/span><span class=\"w\">      <\/span><span class=\"nx\">apiServiceName<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">Namespace<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">req<\/span><span class=\"p\">.<\/span><span class=\"nx\">Namespace<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">},<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"c1\">\/\/ Get the service by name<\/span>\n<span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"p\">=<\/span><span class=\"w\"> <\/span><span class=\"nx\">r<\/span><span class=\"p\">.<\/span><span class=\"nx\">Client<\/span><span class=\"p\">.<\/span><span class=\"nx\">Get<\/span><span class=\"p\">(<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">ctx<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">client<\/span><span class=\"p\">.<\/span><span class=\"nx\">ObjectKey<\/span><span class=\"p\">{<\/span><span class=\"nx\">Name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiServiceName<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">Namespace<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">req<\/span><span class=\"p\">.<\/span><span class=\"nx\">Namespace<\/span><span class=\"p\">},<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">apiService<\/span><span class=\"p\">)<\/span>\n<span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"o\">!=<\/span><span class=\"w\"> <\/span><span class=\"kc\">nil<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;&amp;<\/span><span class=\"w\"> <\/span><span class=\"nx\">k8s_errors<\/span><span class=\"p\">.<\/span><span class=\"nx\">IsNotFound<\/span><span class=\"p\">(<\/span><span class=\"nx\">err<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">   <\/span><span class=\"c1\">\/\/ Resource is not found<\/span>\n<span class=\"w\">   <\/span><span class=\"c1\">\/\/ Define the Service resource to create<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">apiService<\/span><span class=\"p\">.<\/span><span class=\"nx\">Spec<\/span><span class=\"w\"> <\/span><span class=\"p\">=<\/span><span class=\"w\"> <\/span><span class=\"nx\">corev1<\/span><span class=\"p\">.<\/span><span class=\"nx\">ServiceSpec<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">Ports<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">[]<\/span><span class=\"nx\">corev1<\/span><span class=\"p\">.<\/span><span class=\"nx\">ServicePort<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">            <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                <\/span><span class=\"nx\">Name<\/span><span class=\"p\">:<\/span><span class=\"w\">     <\/span><span class=\"nx\">resourceName<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;api-port&quot;<\/span><span class=\"p\">),<\/span>\n<span class=\"w\">                <\/span><span class=\"nx\">Protocol<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">corev1<\/span><span class=\"p\">.<\/span><span class=\"nx\">ProtocolTCP<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">                <\/span><span class=\"nx\">Port<\/span><span class=\"p\">:<\/span><span class=\"w\">     <\/span><span class=\"nb\">int32<\/span><span class=\"p\">(<\/span><span class=\"nx\">apiPort<\/span><span class=\"p\">),<\/span>\n<span class=\"w\">            <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">      <\/span><span class=\"c1\">\/\/ The labels used to discover deployment&#39; Pods<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">Selector<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiMatchLabels<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">   <\/span><span class=\"c1\">\/\/ Add an owner reference (Monocle instance) on the service resource<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"nx\">ctrl_util<\/span><span class=\"p\">.<\/span><span class=\"nx\">SetControllerReference<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">instance<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">apiService<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">r<\/span><span class=\"p\">.<\/span><span class=\"nx\">Scheme<\/span><span class=\"p\">);<\/span><span class=\"w\"> <\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"o\">!=<\/span><span class=\"w\"> <\/span><span class=\"kc\">nil<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">logger<\/span><span class=\"p\">.<\/span><span class=\"nx\">Info<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Unable to set controller reference&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;name&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiServiceName<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">        <\/span><span class=\"k\">return<\/span><span class=\"w\"> <\/span><span class=\"nx\">reconcileLater<\/span><span class=\"p\">(<\/span><span class=\"nx\">err<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">logger<\/span><span class=\"p\">.<\/span><span class=\"nx\">Info<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Creating Service&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;name&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiServiceName<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">   <\/span><span class=\"c1\">\/\/ Create the resource<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"nx\">r<\/span><span class=\"p\">.<\/span><span class=\"nx\">Create<\/span><span class=\"p\">(<\/span><span class=\"nx\">ctx<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">apiService<\/span><span class=\"p\">);<\/span><span class=\"w\"> <\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"o\">!=<\/span><span class=\"w\"> <\/span><span class=\"kc\">nil<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">logger<\/span><span class=\"p\">.<\/span><span class=\"nx\">Info<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Unable to create service&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;name&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiService<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">        <\/span><span class=\"k\">return<\/span><span class=\"w\"> <\/span><span class=\"nx\">reconcileLater<\/span><span class=\"p\">(<\/span><span class=\"nx\">err<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span><span class=\"w\"> <\/span><span class=\"k\">else<\/span><span class=\"w\"> <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"o\">!=<\/span><span class=\"w\"> <\/span><span class=\"kc\">nil<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ Handle the unexpected err<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">logger<\/span><span class=\"p\">.<\/span><span class=\"nx\">Info<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Unable to get resource&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;name&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiServiceName<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">return<\/span><span class=\"w\"> <\/span><span class=\"nx\">reconcileLater<\/span><span class=\"p\">(<\/span><span class=\"nx\">err<\/span><span class=\"p\">)<\/span>\n<span class=\"p\">}<\/span><span class=\"w\"> <\/span><span class=\"k\">else<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ Eventually handle resource update<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">logger<\/span><span class=\"p\">.<\/span><span class=\"nx\">Info<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Resource fetched successfuly&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;name&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiServiceName<\/span><span class=\"p\">)<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<p>Now let's see how the Monocle API is deployed. It leverages the\n<tt class=\"docutils literal\">Deployment<\/tt> resource to start a <tt class=\"docutils literal\">Pod<\/tt> containing one <tt class=\"docutils literal\">Monocle<\/tt>\ncontainer based on the upstream container image.<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"c1\">\/\/ Handle API deployment \/\/<\/span>\n<span class=\"c1\">\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/<\/span>\n\n<span class=\"c1\">\/\/ Service resource name<\/span>\n<span class=\"nx\">apiDeploymentName<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"nx\">resourceName<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;api&quot;<\/span><span class=\"p\">)<\/span>\n<span class=\"nx\">apiDeployment<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"nx\">appsv1<\/span><span class=\"p\">.<\/span><span class=\"nx\">Deployment<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">ObjectMeta<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">metav1<\/span><span class=\"p\">.<\/span><span class=\"nx\">ObjectMeta<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">Name<\/span><span class=\"p\">:<\/span><span class=\"w\">      <\/span><span class=\"nx\">apiDeploymentName<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">Namespace<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">req<\/span><span class=\"p\">.<\/span><span class=\"nx\">Namespace<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">},<\/span>\n<span class=\"p\">}<\/span>\n<span class=\"nx\">apiReplicasCount<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"nb\">int32<\/span><span class=\"p\">(<\/span><span class=\"mi\">1<\/span><span class=\"p\">)<\/span>\n\n<span class=\"c1\">\/\/ We read the Monocle Public URL value passed via the CRD&#39;s spec<\/span>\n<span class=\"nx\">monoclePublicURL<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;http:\/\/localhost:8090&quot;<\/span>\n<span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"nx\">instance<\/span><span class=\"p\">.<\/span><span class=\"nx\">Spec<\/span><span class=\"p\">.<\/span><span class=\"nx\">MonoclePublicURL<\/span><span class=\"w\"> <\/span><span class=\"o\">!=<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;&quot;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">monoclePublicURL<\/span><span class=\"w\"> <\/span><span class=\"p\">=<\/span><span class=\"w\"> <\/span><span class=\"nx\">instance<\/span><span class=\"p\">.<\/span><span class=\"nx\">Spec<\/span><span class=\"p\">.<\/span><span class=\"nx\">MonoclePublicURL<\/span>\n<span class=\"p\">}<\/span>\n<span class=\"nx\">logger<\/span><span class=\"p\">.<\/span><span class=\"nx\">Info<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Monocle public URL set to&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;url&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">monoclePublicURL<\/span><span class=\"p\">)<\/span>\n\n<span class=\"c1\">\/\/ Get the deployment by name<\/span>\n<span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"p\">=<\/span><span class=\"w\"> <\/span><span class=\"nx\">r<\/span><span class=\"p\">.<\/span><span class=\"nx\">Client<\/span><span class=\"p\">.<\/span><span class=\"nx\">Get<\/span><span class=\"p\">(<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">ctx<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">client<\/span><span class=\"p\">.<\/span><span class=\"nx\">ObjectKey<\/span><span class=\"p\">{<\/span><span class=\"nx\">Name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiDeploymentName<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">Namespace<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">req<\/span><span class=\"p\">.<\/span><span class=\"nx\">Namespace<\/span><span class=\"p\">},<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">apiDeployment<\/span><span class=\"p\">)<\/span>\n<span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"o\">!=<\/span><span class=\"w\"> <\/span><span class=\"kc\">nil<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;&amp;<\/span><span class=\"w\"> <\/span><span class=\"nx\">k8s_errors<\/span><span class=\"p\">.<\/span><span class=\"nx\">IsNotFound<\/span><span class=\"p\">(<\/span><span class=\"nx\">err<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ Setup the deployment object<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">apiConfigMapVolumeName<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"nx\">resourceName<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;api-cm-volume&quot;<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ Once created Deployment selector is immutable<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">apiDeployment<\/span><span class=\"p\">.<\/span><span class=\"nx\">Spec<\/span><span class=\"p\">.<\/span><span class=\"nx\">Selector<\/span><span class=\"w\"> <\/span><span class=\"p\">=<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">metav1<\/span><span class=\"p\">.<\/span><span class=\"nx\">LabelSelector<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">      <\/span><span class=\"c1\">\/\/ Enable relation between Pod, Deployment and Service<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">MatchLabels<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiMatchLabels<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ Set replicas count<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">apiDeployment<\/span><span class=\"p\">.<\/span><span class=\"nx\">Spec<\/span><span class=\"p\">.<\/span><span class=\"nx\">Replicas<\/span><span class=\"w\"> <\/span><span class=\"p\">=<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">apiReplicasCount<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ Set the Deployment annotations<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">apiDeployment<\/span><span class=\"p\">.<\/span><span class=\"nx\">Annotations<\/span><span class=\"w\"> <\/span><span class=\"p\">=<\/span><span class=\"w\"> <\/span><span class=\"kd\">map<\/span><span class=\"p\">[<\/span><span class=\"kt\">string<\/span><span class=\"p\">]<\/span><span class=\"kt\">string<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">      <\/span><span class=\"c1\">\/\/ Here we set the Resource version of the Monocle ConfigMap<\/span>\n<span class=\"w\">        <\/span><span class=\"s\">&quot;apiConfigVersion&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiConfigVersion<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ Set the Deployment pod template<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">apiDeployment<\/span><span class=\"p\">.<\/span><span class=\"nx\">Spec<\/span><span class=\"p\">.<\/span><span class=\"nx\">Template<\/span><span class=\"w\"> <\/span><span class=\"p\">=<\/span><span class=\"w\"> <\/span><span class=\"nx\">corev1<\/span><span class=\"p\">.<\/span><span class=\"nx\">PodTemplateSpec<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">ObjectMeta<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">metav1<\/span><span class=\"p\">.<\/span><span class=\"nx\">ObjectMeta<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">         <\/span><span class=\"c1\">\/\/ Enable relation between Pod, Deployment and Service<\/span>\n<span class=\"w\">            <\/span><span class=\"nx\">Labels<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiMatchLabels<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">         <\/span><span class=\"c1\">\/\/ Here we set the Resource version of the Monocle secrets<\/span>\n<span class=\"w\">         <\/span><span class=\"c1\">\/\/ Any update on the Template (here the annotation) starts a rollout<\/span>\n<span class=\"w\">            <\/span><span class=\"nx\">Annotations<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"kd\">map<\/span><span class=\"p\">[<\/span><span class=\"kt\">string<\/span><span class=\"p\">]<\/span><span class=\"kt\">string<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                <\/span><span class=\"s\">&quot;apiSecretsVersion&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiSecretsVersion<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">            <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">Spec<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">corev1<\/span><span class=\"p\">.<\/span><span class=\"nx\">PodSpec<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">            <\/span><span class=\"nx\">RestartPolicy<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">corev1<\/span><span class=\"p\">.<\/span><span class=\"nx\">RestartPolicyAlways<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">            <\/span><span class=\"nx\">Containers<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">[]<\/span><span class=\"nx\">corev1<\/span><span class=\"p\">.<\/span><span class=\"nx\">Container<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                    <\/span><span class=\"nx\">Name<\/span><span class=\"p\">:<\/span><span class=\"w\">    <\/span><span class=\"nx\">resourceName<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;api-pod&quot;<\/span><span class=\"p\">),<\/span>\n<span class=\"w\">                    <\/span><span class=\"nx\">Image<\/span><span class=\"p\">:<\/span><span class=\"w\">   <\/span><span class=\"s\">&quot;quay.io\/change-metrics\/monocle:1.8.0&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">                    <\/span><span class=\"nx\">Command<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">[]<\/span><span class=\"kt\">string<\/span><span class=\"p\">{<\/span><span class=\"s\">&quot;monocle&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;api&quot;<\/span><span class=\"p\">},<\/span>\n<span class=\"w\">               <\/span><span class=\"c1\">\/\/ This exposes the Secret as environment variables into the running container<\/span>\n<span class=\"w\">                    <\/span><span class=\"nx\">EnvFrom<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">[]<\/span><span class=\"nx\">corev1<\/span><span class=\"p\">.<\/span><span class=\"nx\">EnvFromSource<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                        <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                            <\/span><span class=\"nx\">SecretRef<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">corev1<\/span><span class=\"p\">.<\/span><span class=\"nx\">SecretEnvSource<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                                <\/span><span class=\"nx\">LocalObjectReference<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">corev1<\/span><span class=\"p\">.<\/span><span class=\"nx\">LocalObjectReference<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                                    <\/span><span class=\"nx\">Name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiSecretName<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">                                <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">                            <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">                        <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">                    <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">               <\/span><span class=\"c1\">\/\/ An additional environment variable<\/span>\n<span class=\"w\">                    <\/span><span class=\"nx\">Env<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">[]<\/span><span class=\"nx\">corev1<\/span><span class=\"p\">.<\/span><span class=\"nx\">EnvVar<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                        <\/span><span class=\"nx\">elasticUrlEnvVar<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">                        <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                            <\/span><span class=\"nx\">Name<\/span><span class=\"p\">:<\/span><span class=\"w\">  <\/span><span class=\"s\">&quot;MONOCLE_PUBLIC_URL&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">                            <\/span><span class=\"nx\">Value<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">monoclePublicURL<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">                        <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">                    <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">               <\/span><span class=\"c1\">\/\/ We defines ports exposed by the container<\/span>\n<span class=\"w\">                    <\/span><span class=\"nx\">Ports<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">[]<\/span><span class=\"nx\">corev1<\/span><span class=\"p\">.<\/span><span class=\"nx\">ContainerPort<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                        <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                            <\/span><span class=\"nx\">ContainerPort<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nb\">int32<\/span><span class=\"p\">(<\/span><span class=\"nx\">apiPort<\/span><span class=\"p\">),<\/span>\n<span class=\"w\">                        <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">                    <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">               <\/span><span class=\"c1\">\/\/ Define the live test probe<\/span>\n<span class=\"w\">               <\/span><span class=\"c1\">\/\/ The Monocle API exposes the &#39;\/health&#39; endpoint<\/span>\n<span class=\"w\">                    <\/span><span class=\"nx\">LivenessProbe<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">corev1<\/span><span class=\"p\">.<\/span><span class=\"nx\">Probe<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                        <\/span><span class=\"nx\">ProbeHandler<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">corev1<\/span><span class=\"p\">.<\/span><span class=\"nx\">ProbeHandler<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                            <\/span><span class=\"nx\">HTTPGet<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">corev1<\/span><span class=\"p\">.<\/span><span class=\"nx\">HTTPGetAction<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                                <\/span><span class=\"nx\">Path<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;\/health&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">                                <\/span><span class=\"nx\">Port<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">intstr<\/span><span class=\"p\">.<\/span><span class=\"nx\">FromInt<\/span><span class=\"p\">(<\/span><span class=\"nx\">apiPort<\/span><span class=\"p\">),<\/span>\n<span class=\"w\">                            <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">                        <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">                        <\/span><span class=\"nx\">TimeoutSeconds<\/span><span class=\"p\">:<\/span><span class=\"w\">   <\/span><span class=\"mi\">30<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">                        <\/span><span class=\"nx\">FailureThreshold<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"mi\">6<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">                    <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">               <\/span><span class=\"c1\">\/\/ A Volume device is exposed to the container<\/span>\n<span class=\"w\">               <\/span><span class=\"c1\">\/\/ We mount it into \/etc\/monocle. It contains the Monocle config file.<\/span>\n<span class=\"w\">                    <\/span><span class=\"nx\">VolumeMounts<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">[]<\/span><span class=\"nx\">corev1<\/span><span class=\"p\">.<\/span><span class=\"nx\">VolumeMount<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                        <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                            <\/span><span class=\"nx\">Name<\/span><span class=\"p\">:<\/span><span class=\"w\">      <\/span><span class=\"nx\">apiConfigMapVolumeName<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">                            <\/span><span class=\"nx\">ReadOnly<\/span><span class=\"p\">:<\/span><span class=\"w\">  <\/span><span class=\"kc\">true<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">                            <\/span><span class=\"nx\">MountPath<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;\/etc\/monocle&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">                        <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">                    <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">                <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">            <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">         <\/span><span class=\"c1\">\/\/ Expose a Volume device to the Pod&#39; containers<\/span>\n<span class=\"w\">         <\/span><span class=\"c1\">\/\/ The Volume is the API ConfigMap that we expose as a volume.<\/span>\n<span class=\"w\">            <\/span><span class=\"nx\">Volumes<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">[]<\/span><span class=\"nx\">corev1<\/span><span class=\"p\">.<\/span><span class=\"nx\">Volume<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                    <\/span><span class=\"nx\">Name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiConfigMapVolumeName<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">                    <\/span><span class=\"nx\">VolumeSource<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">corev1<\/span><span class=\"p\">.<\/span><span class=\"nx\">VolumeSource<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                        <\/span><span class=\"nx\">ConfigMap<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">corev1<\/span><span class=\"p\">.<\/span><span class=\"nx\">ConfigMapVolumeSource<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                            <\/span><span class=\"nx\">LocalObjectReference<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">corev1<\/span><span class=\"p\">.<\/span><span class=\"nx\">LocalObjectReference<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                                <\/span><span class=\"nx\">Name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiConfigMapName<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">                            <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">                        <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">                    <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">                <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">            <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">   <\/span><span class=\"c1\">\/\/ Add an owner reference (Monocle instance) on the deployment resource<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"nx\">ctrl_util<\/span><span class=\"p\">.<\/span><span class=\"nx\">SetControllerReference<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">instance<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">apiDeployment<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">r<\/span><span class=\"p\">.<\/span><span class=\"nx\">Scheme<\/span><span class=\"p\">);<\/span><span class=\"w\"> <\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"o\">!=<\/span><span class=\"w\"> <\/span><span class=\"kc\">nil<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">logger<\/span><span class=\"p\">.<\/span><span class=\"nx\">Info<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Unable to set controller reference&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;name&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiDeploymentName<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">        <\/span><span class=\"k\">return<\/span><span class=\"w\"> <\/span><span class=\"nx\">reconcileLater<\/span><span class=\"p\">(<\/span><span class=\"nx\">err<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">logger<\/span><span class=\"p\">.<\/span><span class=\"nx\">Info<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Creating Deployment&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;name&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiDeploymentName<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ Create the resource<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"nx\">r<\/span><span class=\"p\">.<\/span><span class=\"nx\">Create<\/span><span class=\"p\">(<\/span><span class=\"nx\">ctx<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">apiDeployment<\/span><span class=\"p\">);<\/span><span class=\"w\"> <\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"o\">!=<\/span><span class=\"w\"> <\/span><span class=\"kc\">nil<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">logger<\/span><span class=\"p\">.<\/span><span class=\"nx\">Info<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Unable to create deployment&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;name&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiDeploymentName<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">        <\/span><span class=\"k\">return<\/span><span class=\"w\"> <\/span><span class=\"nx\">reconcileLater<\/span><span class=\"p\">(<\/span><span class=\"nx\">err<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span><span class=\"w\"> <\/span><span class=\"k\">else<\/span><span class=\"w\"> <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"o\">!=<\/span><span class=\"w\"> <\/span><span class=\"kc\">nil<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ Handle the unexpected err<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">logger<\/span><span class=\"p\">.<\/span><span class=\"nx\">Info<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Unable to get resource&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;name&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiDeploymentName<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">return<\/span><span class=\"w\"> <\/span><span class=\"nx\">reconcileLater<\/span><span class=\"p\">(<\/span><span class=\"nx\">err<\/span><span class=\"p\">)<\/span>\n<span class=\"p\">}<\/span><span class=\"w\"> <\/span><span class=\"k\">else<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ Eventually handle resource update<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">logger<\/span><span class=\"p\">.<\/span><span class=\"nx\">Info<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Resource fetched successfuly&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;name&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiDeploymentName<\/span><span class=\"p\">)<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<p>Some key points that are important here:<\/p>\n<ul class=\"simple\">\n<li>The <tt class=\"docutils literal\">Deployment<\/tt> ensures that we always have a working <tt class=\"docutils literal\">Pod<\/tt> that\nserves the Monocle API.<\/li>\n<li>The <a class=\"reference external\" href=\"https:\/\/kubernetes.io\/docs\/tasks\/configure-pod-container\/configure-liveness-readiness-startup-probes\/\">liveness probe<\/a> is used by the <tt class=\"docutils literal\">Deployment<\/tt> to ensure the\nMonocle API is ready. The <tt class=\"docutils literal\">Deployment<\/tt>'s status is based on the\nprobe's status.<\/li>\n<li>We expose the configuration file from a <a class=\"reference external\" href=\"https:\/\/kubernetes.io\/docs\/concepts\/configuration\/configmap\/\">ConfigMap<\/a> using a\n<a class=\"reference external\" href=\"https:\/\/kubernetes.io\/docs\/tasks\/configure-pod-container\/configure-pod-configmap\/#add-configmap-data-to-a-volume\">volume<\/a>. When the <tt class=\"docutils literal\">configMap<\/tt>'s data is updated, the exposed\nfiles as volume's mount are automatically updated.<\/li>\n<li>We expose the <tt class=\"docutils literal\">Secret<\/tt> resource containing Monocle' secrets <a class=\"reference external\" href=\"https:\/\/kubernetes.io\/docs\/tasks\/inject-data-application\/distribute-credentials-secure\/#configure-all-key-value-pairs-in-a-secret-as-container-environment-variables\">as\nenvironment variables<\/a>.<\/li>\n<\/ul>\n<p>Assuming that others Monocle' services are set up in the controller we\ncan inspect <tt class=\"docutils literal\">Resources<\/tt> spawned by the <tt class=\"docutils literal\">controller<\/tt> when we reclaim\na <tt class=\"docutils literal\">Monocle<\/tt> resource.<\/p>\n<div class=\"highlight\"><pre><span><\/span>$<span class=\"w\"> <\/span>cat<span class=\"w\"> <\/span>config\/samples\/monocle_v1alpha1_monocle-alt.yaml\napiVersion:<span class=\"w\"> <\/span>monocle.monocle.change-metrics.io\/v1alpha1\nkind:<span class=\"w\"> <\/span>Monocle\nmetadata:\n<span class=\"w\">  <\/span>labels:<span class=\"w\">    <\/span>app.kubernetes.io\/name:<span class=\"w\"> <\/span>monocle\n<span class=\"w\">    <\/span>app.kubernetes.io\/instance:<span class=\"w\"> <\/span>monocle-sample\n<span class=\"w\">    <\/span>app.kubernetes.io\/part-of:<span class=\"w\"> <\/span>monocle-operator\n<span class=\"w\">    <\/span>app.kubernetes.io\/managed-by:<span class=\"w\"> <\/span>kustomize\n<span class=\"w\">    <\/span>app.kubernetes.io\/created-by:<span class=\"w\"> <\/span>monocle-operator\n<span class=\"w\">  <\/span>name:<span class=\"w\"> <\/span>monocle-samplespec:\n<span class=\"w\">  <\/span>monoclePublicURL:<span class=\"w\"> <\/span><span class=\"s2\">&quot;http:\/\/localhost:8090&quot;<\/span>\n$<span class=\"w\"> <\/span>kubectl<span class=\"w\"> <\/span>apply<span class=\"w\"> <\/span>-f<span class=\"w\"> <\/span>config\/samples\/monocle_v1alpha1_monocle-alt.yaml\n$<span class=\"w\"> <\/span>kubectl<span class=\"w\"> <\/span>get<span class=\"w\"> <\/span>statefulset,deployment,replicaset,service,configmap,secret\nNAME<span class=\"w\">                                      <\/span>READY<span class=\"w\">   <\/span>AGE\nstatefulset.apps\/monocle-sample-elastic<span class=\"w\">   <\/span><span class=\"m\">1<\/span>\/1<span class=\"w\">     <\/span>15s\n\nNAME<span class=\"w\">                                     <\/span>READY<span class=\"w\">   <\/span>UP-TO-DATE<span class=\"w\">   <\/span>AVAILABLE<span class=\"w\">   <\/span>AGE\ndeployment.apps\/monocle-sample-api<span class=\"w\">       <\/span><span class=\"m\">1<\/span>\/1<span class=\"w\">     <\/span><span class=\"m\">1<\/span><span class=\"w\">            <\/span><span class=\"m\">1<\/span><span class=\"w\">           <\/span>15s\ndeployment.apps\/monocle-sample-crawler<span class=\"w\">   <\/span><span class=\"m\">1<\/span>\/1<span class=\"w\">     <\/span><span class=\"m\">1<\/span><span class=\"w\">            <\/span><span class=\"m\">1<\/span><span class=\"w\">           <\/span>15s\n\nNAME<span class=\"w\">                                                <\/span>DESIRED<span class=\"w\">   <\/span>CURRENT<span class=\"w\">   <\/span>READY<span class=\"w\">   <\/span>AGE\nreplicaset.apps\/monocle-sample-api-8cd74454f<span class=\"w\">        <\/span><span class=\"m\">1<\/span><span class=\"w\">         <\/span><span class=\"m\">1<\/span><span class=\"w\">         <\/span><span class=\"m\">1<\/span><span class=\"w\">       <\/span>15s\nreplicaset.apps\/monocle-sample-crawler-7fc7f659b7<span class=\"w\">   <\/span><span class=\"m\">1<\/span><span class=\"w\">         <\/span><span class=\"m\">1<\/span><span class=\"w\">         <\/span><span class=\"m\">1<\/span><span class=\"w\">       <\/span>15s\n\nNAME<span class=\"w\">                             <\/span>TYPE<span class=\"w\">        <\/span>CLUSTER-IP<span class=\"w\">     <\/span>EXTERNAL-IP<span class=\"w\">   <\/span>PORT<span class=\"o\">(<\/span>S<span class=\"o\">)<\/span><span class=\"w\">    <\/span>AGE\nservice\/monocle-sample-api<span class=\"w\">       <\/span>ClusterIP<span class=\"w\">   <\/span><span class=\"m\">10<\/span>.96.36.244<span class=\"w\">   <\/span>&lt;none&gt;<span class=\"w\">        <\/span><span class=\"m\">8080<\/span>\/TCP<span class=\"w\">   <\/span>15s\nservice\/monocle-sample-elastic<span class=\"w\">   <\/span>ClusterIP<span class=\"w\">   <\/span><span class=\"m\">10<\/span>.96.68.155<span class=\"w\">   <\/span>&lt;none&gt;<span class=\"w\">        <\/span><span class=\"m\">9200<\/span>\/TCP<span class=\"w\">   <\/span>15s\n\nNAME<span class=\"w\">                           <\/span>DATA<span class=\"w\">   <\/span>AGE\nconfigmap\/kube-root-ca.crt<span class=\"w\">     <\/span><span class=\"m\">1<\/span><span class=\"w\">      <\/span>21h\nconfigmap\/monocle-sample-api<span class=\"w\">   <\/span><span class=\"m\">1<\/span><span class=\"w\">      <\/span>15s\n\nNAME<span class=\"w\">                        <\/span>TYPE<span class=\"w\">     <\/span>DATA<span class=\"w\">   <\/span>AGE\nsecret\/monocle-sample-api<span class=\"w\">   <\/span>Opaque<span class=\"w\">   <\/span><span class=\"m\">1<\/span><span class=\"w\">      <\/span>15s\n<\/pre><\/div>\n<p>Accessing the Monocle WEB UI access served by the API can be locally\ndone using a <tt class=\"docutils literal\"><span class=\"pre\">port-forward<\/span><\/tt>:<\/p>\n<div class=\"highlight\"><pre><span><\/span>$<span class=\"w\"> <\/span>kubectl<span class=\"w\"> <\/span>port-forward<span class=\"w\"> <\/span>service\/monocle-sample-api<span class=\"w\"> <\/span><span class=\"m\">8090<\/span>:8080\n$<span class=\"w\"> <\/span>firefox<span class=\"w\"> <\/span>http:\/\/localhost:8090\n<\/pre><\/div>\n<\/div>\n<\/div>\n<div class=\"section\" id=\"how-the-operator-handles-monocle-reconfigurations\">\n<h3>How the operator handles Monocle' reconfigurations<\/h3>\n<p>Now let's see how we handled the (re-)configuration workflow.<\/p>\n<p>As <a class=\"reference external\" href=\"#configuration\">described previously<\/a> we need to handle:<\/p>\n<ul class=\"simple\">\n<li>A change to the Monocle secrets (stored in a <tt class=\"docutils literal\">Secret<\/tt> resource)\nrestarts the API and the Crawler <tt class=\"docutils literal\">Pods<\/tt>.<\/li>\n<li>A change to the Monocle config file (stored in a <tt class=\"docutils literal\">ConfigMap<\/tt>\nresource) triggers the <tt class=\"docutils literal\"><span class=\"pre\">update-idents<\/span><\/tt> CLI command.<\/li>\n<\/ul>\n<div class=\"section\" id=\"handling-secret-changes\">\n<h4>Handling Secret changes<\/h4>\n<p>API and Crawler processes are handled by the <a class=\"reference external\" href=\"https:\/\/kubernetes.io\/docs\/concepts\/workloads\/controllers\/deployment\/\">Deployment Resource<\/a>.\nThis resource's controller handles a <a class=\"reference external\" href=\"https:\/\/kubernetes.io\/docs\/concepts\/workloads\/controllers\/deployment\/#updating-a-deployment\">rollout<\/a> workflow when the\n<tt class=\"docutils literal\">podSpec<\/tt>'s <tt class=\"docutils literal\">Image<\/tt> field or the <tt class=\"docutils literal\">podTemplateSpec<\/tt>'s annotations\nare updated. A <tt class=\"docutils literal\">rollout<\/tt> restarts <tt class=\"docutils literal\">Pods<\/tt> in safe manner according to\nthe configured rollout strategy.<\/p>\n<p>To ensure that API and Crawlers containers are restarted when the\nMonocle's administrator changes the secrets we use an annotation (we\nonly focus on the API, the same applies for the Crawler's Deployment):<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"c1\">\/\/ else case (an API Deployment resource exists) of the API deployment part<\/span>\n<span class=\"p\">}<\/span><span class=\"w\"> <\/span><span class=\"k\">else<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ Eventually handle resource update<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">logger<\/span><span class=\"p\">.<\/span><span class=\"nx\">Info<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Resource fetched successfuly&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;name&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiDeploymentName<\/span><span class=\"p\">)<\/span>\n\n<span class=\"w\">   <\/span><span class=\"c1\">\/\/ We call the rollOutWhenApiSecretsChange function<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"nx\">r<\/span><span class=\"p\">.<\/span><span class=\"nx\">rollOutWhenApiSecretsChange<\/span><span class=\"p\">(<\/span><span class=\"nx\">ctx<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">logger<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiDeployment<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiSecretsVersion<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"o\">!=<\/span><span class=\"w\"> <\/span><span class=\"kc\">nil<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">logger<\/span><span class=\"p\">.<\/span><span class=\"nx\">Info<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Unable to update spec deployment annotations&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;name&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiDeploymentName<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">reconcileLater<\/span><span class=\"p\">(<\/span><span class=\"nx\">err<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<div class=\"highlight\"><pre><span><\/span><span class=\"kd\">func<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"nx\">r<\/span><span class=\"w\"> <\/span><span class=\"o\">*<\/span><span class=\"nx\">MonocleReconciler<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"nx\">rollOutWhenApiSecretsChange<\/span><span class=\"p\">(<\/span><span class=\"nx\">ctx<\/span><span class=\"w\"> <\/span><span class=\"nx\">context<\/span><span class=\"p\">.<\/span><span class=\"nx\">Context<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">logger<\/span><span class=\"w\"> <\/span><span class=\"nx\">logr<\/span><span class=\"p\">.<\/span><span class=\"nx\">Logger<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">depl<\/span><span class=\"w\"> <\/span><span class=\"nx\">appsv1<\/span><span class=\"p\">.<\/span><span class=\"nx\">Deployment<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiSecretsVersion<\/span><span class=\"w\"> <\/span><span class=\"kt\">string<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"kt\">error<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">previousSecretsVersion<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"nx\">depl<\/span><span class=\"p\">.<\/span><span class=\"nx\">Spec<\/span><span class=\"p\">.<\/span><span class=\"nx\">Template<\/span><span class=\"p\">.<\/span><span class=\"nx\">Annotations<\/span><span class=\"p\">[<\/span><span class=\"s\">&quot;apiSecretsVersion&quot;<\/span><span class=\"p\">]<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"nx\">previousSecretsVersion<\/span><span class=\"w\"> <\/span><span class=\"o\">!=<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiSecretsVersion<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">logger<\/span><span class=\"p\">.<\/span><span class=\"nx\">Info<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Start a rollout due to secrets update&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">            <\/span><span class=\"s\">&quot;name&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">depl<\/span><span class=\"p\">.<\/span><span class=\"nx\">Name<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">            <\/span><span class=\"s\">&quot;previous secrets version&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">previousSecretsVersion<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">            <\/span><span class=\"s\">&quot;new secrets version&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiSecretsVersion<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">depl<\/span><span class=\"p\">.<\/span><span class=\"nx\">Spec<\/span><span class=\"p\">.<\/span><span class=\"nx\">Template<\/span><span class=\"p\">.<\/span><span class=\"nx\">Annotations<\/span><span class=\"p\">[<\/span><span class=\"s\">&quot;apiSecretsVersion&quot;<\/span><span class=\"p\">]<\/span><span class=\"w\"> <\/span><span class=\"p\">=<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiSecretsVersion<\/span>\n<span class=\"w\">        <\/span><span class=\"k\">return<\/span><span class=\"w\"> <\/span><span class=\"nx\">r<\/span><span class=\"p\">.<\/span><span class=\"nx\">Update<\/span><span class=\"p\">(<\/span><span class=\"nx\">ctx<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">depl<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">return<\/span><span class=\"w\"> <\/span><span class=\"kc\">nil<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<p>At <tt class=\"docutils literal\">Deployment<\/tt> creation we set an annotation called:\n<tt class=\"docutils literal\">apiSecretsVersion<\/tt>, and every time the <tt class=\"docutils literal\">Reconcile<\/tt> function is\ncalled the <tt class=\"docutils literal\">rollOutWhenApiSecretsChange<\/tt> function checks if the\nresource version changed. In the case of a change (meaning that the\nadministrator changed one of the Monocle's secrets) we do an <tt class=\"docutils literal\">Update<\/tt>\nof the annotation and store the new <tt class=\"docutils literal\">apiSecretsVersion<\/tt> value.<\/p>\n<p>This triggers the <tt class=\"docutils literal\">Deployments<\/tt> rollout process, where the current\ncontainers in a <tt class=\"docutils literal\">Pod<\/tt> are terminated and the new ones are created. In\nthis case with the <tt class=\"docutils literal\">apiSecretsVersion<\/tt> annotation value updated.<\/p>\n<p>This can be observed by editing secrets to add a new one, then ensuring\npods are re-spawned and that the new secret is available in the <tt class=\"docutils literal\">env<\/tt>\nof the pod's container:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"c1\"># A secret value must be encoded as base64<\/span>\n$<span class=\"w\"> <\/span>kubectl<span class=\"w\"> <\/span>edit<span class=\"w\"> <\/span>secrets<span class=\"w\"> <\/span>monocle-sample-api\n$<span class=\"w\"> <\/span>kubectl<span class=\"w\"> <\/span>get<span class=\"w\"> <\/span>pods\n$<span class=\"w\"> <\/span>kubectl<span class=\"w\"> <\/span><span class=\"nb\">exec<\/span><span class=\"w\"> <\/span>-it<span class=\"w\"> <\/span>monocle-sample-api-c75dcc789-gmwwm<span class=\"w\"> <\/span>--<span class=\"w\"> <\/span>env<span class=\"w\"> <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span>grep<span class=\"w\"> <\/span>-i<span class=\"w\"> <\/span>&lt;new-secret&gt;\n<\/pre><\/div>\n<p>To configure the controller to call the <tt class=\"docutils literal\">Reconcile<\/tt> function when a\ndependent resource is changed, we need to sets up the <a class=\"reference external\" href=\"https:\/\/pkg.go.dev\/sigs.k8s.io\/controller-runtime#hdr-Managers\">Manager<\/a> this\nway:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"c1\">\/\/ SetupWithManager sets up the controller with the Manager.<\/span>\n<span class=\"kd\">func<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"nx\">r<\/span><span class=\"w\"> <\/span><span class=\"o\">*<\/span><span class=\"nx\">MonocleReconciler<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"nx\">SetupWithManager<\/span><span class=\"p\">(<\/span><span class=\"nx\">mgr<\/span><span class=\"w\"> <\/span><span class=\"nx\">ctrl<\/span><span class=\"p\">.<\/span><span class=\"nx\">Manager<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"kt\">error<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">return<\/span><span class=\"w\"> <\/span><span class=\"nx\">ctrl<\/span><span class=\"p\">.<\/span><span class=\"nx\">NewControllerManagedBy<\/span><span class=\"p\">(<\/span><span class=\"nx\">mgr<\/span><span class=\"p\">).<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">For<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">monoclev1alpha1<\/span><span class=\"p\">.<\/span><span class=\"nx\">Monocle<\/span><span class=\"p\">{}).<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">Owns<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">appsv1<\/span><span class=\"p\">.<\/span><span class=\"nx\">Deployment<\/span><span class=\"p\">{}).<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">Owns<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">corev1<\/span><span class=\"p\">.<\/span><span class=\"nx\">ConfigMap<\/span><span class=\"p\">{}).<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">Owns<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">corev1<\/span><span class=\"p\">.<\/span><span class=\"nx\">Secret<\/span><span class=\"p\">{}).<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">Owns<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">appsv1<\/span><span class=\"p\">.<\/span><span class=\"nx\">StatefulSet<\/span><span class=\"p\">{}).<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">Owns<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">corev1<\/span><span class=\"p\">.<\/span><span class=\"nx\">Service<\/span><span class=\"p\">{}).<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">Complete<\/span><span class=\"p\">(<\/span><span class=\"nx\">r<\/span><span class=\"p\">)<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<p>The <a class=\"reference external\" href=\"https:\/\/pkg.go.dev\/sigs.k8s.io\/controller-runtime\/pkg\/builder#Builder.Owns\">Owns<\/a> coupled to the <a class=\"reference external\" href=\"https:\/\/kubernetes.io\/docs\/concepts\/overview\/working-with-objects\/owners-dependents\/\">owner references<\/a> ensure that the\n<tt class=\"docutils literal\">Reconcile<\/tt> function is called when a dependent resource is updated.<\/p>\n<\/div>\n<div class=\"section\" id=\"handling-config-changes\">\n<h4>Handling Config changes<\/h4>\n<p>The <tt class=\"docutils literal\">ConfigMap<\/tt> that stores the Monocle's config <tt class=\"docutils literal\">config.yaml<\/tt> is\nexposed as a <tt class=\"docutils literal\">Volume Mount<\/tt> in <tt class=\"docutils literal\">\/etc\/monocle<\/tt> and Monocle knows how\nto reload itself when its file is changed.<\/p>\n<p>However we still need to detect updates on the <tt class=\"docutils literal\">ConfigMap<\/tt> and start a\nMonocle's CLI command to <a class=\"reference external\" href=\"https:\/\/github.com\/change-metrics\/monocle#apply-idents-configuration\">update idents<\/a>. To do that we use a <a class=\"reference external\" href=\"https:\/\/kubernetes.io\/docs\/concepts\/workloads\/controllers\/job\/\">Job<\/a>\nResource (<a class=\"reference external\" href=\"https:\/\/pkg.go.dev\/k8s.io\/api\/batch\/v1#Job\">type<\/a>).<\/p>\n<p>The <tt class=\"docutils literal\">Job<\/tt> starts a <tt class=\"docutils literal\">Pod<\/tt> and reports execution status of the\ncontainer's command.<\/p>\n<p>Similarly to the Monocle secrets, we store, in an annotation\n(<tt class=\"docutils literal\">apiConfigVersion<\/tt>) on the API <tt class=\"docutils literal\">Deployment<\/tt> resource, the\n<tt class=\"docutils literal\">ResourceVersion<\/tt> of the <tt class=\"docutils literal\">ConfigMap<\/tt> and by checking for a version\nchange we can create a <tt class=\"docutils literal\">Job<\/tt> resource and trigger the CLI command.<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"c1\">\/\/ else case (an API Deployment resource exists) of the API deployment part<\/span>\n<span class=\"p\">}<\/span><span class=\"w\"> <\/span><span class=\"k\">else<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ Eventually handle resource update<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">logger<\/span><span class=\"p\">.<\/span><span class=\"nx\">Info<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Resource fetched successfuly&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;name&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiDeploymentName<\/span><span class=\"p\">)<\/span>\n\n<span class=\"w\">   <\/span><span class=\"o\">...<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ Check if Deployment Pod Annotation for ConfigMap resource version was updated<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">previousVersion<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiDeployment<\/span><span class=\"p\">.<\/span><span class=\"nx\">Annotations<\/span><span class=\"p\">[<\/span><span class=\"s\">&quot;apiConfigVersion&quot;<\/span><span class=\"p\">]<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"nx\">previousVersion<\/span><span class=\"w\"> <\/span><span class=\"o\">!=<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiConfigVersion<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n\n<span class=\"w\">        <\/span><span class=\"nx\">logger<\/span><span class=\"p\">.<\/span><span class=\"nx\">Info<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Start the update-idents jobs because of api configMap update&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">            <\/span><span class=\"s\">&quot;name&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiDeployment<\/span><span class=\"p\">.<\/span><span class=\"nx\">Name<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">            <\/span><span class=\"s\">&quot;previous configmap version&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">previousVersion<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">            <\/span><span class=\"s\">&quot;new configmap version&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiConfigVersion<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">apiDeployment<\/span><span class=\"p\">.<\/span><span class=\"nx\">Annotations<\/span><span class=\"p\">[<\/span><span class=\"s\">&quot;apiConfigVersion&quot;<\/span><span class=\"p\">]<\/span><span class=\"w\"> <\/span><span class=\"p\">=<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiConfigVersion<\/span>\n<span class=\"w\">        <\/span><span class=\"c1\">\/\/ Update Deployment Resource to set the new configMap resource version<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"nx\">r<\/span><span class=\"p\">.<\/span><span class=\"nx\">Update<\/span><span class=\"p\">(<\/span><span class=\"nx\">ctx<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">apiDeployment<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">        <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"o\">!=<\/span><span class=\"w\"> <\/span><span class=\"kc\">nil<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">            <\/span><span class=\"k\">return<\/span><span class=\"w\"> <\/span><span class=\"nx\">reconcileLater<\/span><span class=\"p\">(<\/span><span class=\"nx\">err<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">        <\/span><span class=\"c1\">\/\/ Trigger the job<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"p\">=<\/span><span class=\"w\"> <\/span><span class=\"nx\">triggerUpdateIdentsJob<\/span><span class=\"p\">(<\/span><span class=\"nx\">r<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">ctx<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">instance<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">req<\/span><span class=\"p\">.<\/span><span class=\"nx\">Namespace<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">logger<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">elasticUrlEnvVar<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiConfigMapName<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">        <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"o\">!=<\/span><span class=\"w\"> <\/span><span class=\"kc\">nil<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">            <\/span><span class=\"nx\">logger<\/span><span class=\"p\">.<\/span><span class=\"nx\">Info<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Unable to trigger update-idents&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;name&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">err<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">            <\/span><span class=\"nx\">reconcileLater<\/span><span class=\"p\">(<\/span><span class=\"nx\">err<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<div class=\"highlight\"><pre><span><\/span><span class=\"kd\">func<\/span><span class=\"w\"> <\/span><span class=\"nx\">triggerUpdateIdentsJob<\/span><span class=\"p\">(<\/span>\n<span class=\"w\">      <\/span><span class=\"nx\">r<\/span><span class=\"w\"> <\/span><span class=\"o\">*<\/span><span class=\"nx\">MonocleReconciler<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">ctx<\/span><span class=\"w\"> <\/span><span class=\"nx\">context<\/span><span class=\"p\">.<\/span><span class=\"nx\">Context<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">instance<\/span><span class=\"w\"> <\/span><span class=\"nx\">monoclev1alpha1<\/span><span class=\"p\">.<\/span><span class=\"nx\">Monocle<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">      <\/span><span class=\"nx\">namespace<\/span><span class=\"w\"> <\/span><span class=\"kt\">string<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">logger<\/span><span class=\"w\"> <\/span><span class=\"nx\">logr<\/span><span class=\"p\">.<\/span><span class=\"nx\">Logger<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">elasticUrlEnvVar<\/span><span class=\"w\"> <\/span><span class=\"nx\">corev1<\/span><span class=\"p\">.<\/span><span class=\"nx\">EnvVar<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiConfigMapName<\/span><span class=\"w\"> <\/span><span class=\"kt\">string<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"kt\">error<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n\n<span class=\"w\">    <\/span><span class=\"nx\">jobname<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;update-idents-job&quot;<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">job<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"nx\">batchv1<\/span><span class=\"p\">.<\/span><span class=\"nx\">Job<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">ObjectMeta<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">metav1<\/span><span class=\"p\">.<\/span><span class=\"nx\">ObjectMeta<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">            <\/span><span class=\"nx\">Name<\/span><span class=\"p\">:<\/span><span class=\"w\">      <\/span><span class=\"nx\">jobname<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">            <\/span><span class=\"nx\">Namespace<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">namespace<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ Checking if there is a Job Resource by Name<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"nx\">r<\/span><span class=\"p\">.<\/span><span class=\"nx\">Client<\/span><span class=\"p\">.<\/span><span class=\"nx\">Get<\/span><span class=\"p\">(<\/span><span class=\"nx\">ctx<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">client<\/span><span class=\"p\">.<\/span><span class=\"nx\">ObjectKey<\/span><span class=\"p\">{<\/span><span class=\"nx\">Name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">jobname<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">Namespace<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">namespace<\/span><span class=\"p\">},<\/span>\n<span class=\"w\">        <\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">job<\/span><span class=\"p\">)<\/span>\n\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ Delete it if there is an old job resource<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">fg<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"nx\">metav1<\/span><span class=\"p\">.<\/span><span class=\"nx\">DeletePropagationBackground<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"o\">==<\/span><span class=\"w\"> <\/span><span class=\"kc\">nil<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">r<\/span><span class=\"p\">.<\/span><span class=\"nx\">Client<\/span><span class=\"p\">.<\/span><span class=\"nx\">Delete<\/span><span class=\"p\">(<\/span><span class=\"nx\">ctx<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">            <\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">job<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">client<\/span><span class=\"p\">.<\/span><span class=\"nx\">DeleteOptions<\/span><span class=\"p\">{<\/span><span class=\"nx\">PropagationPolicy<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">fg<\/span><span class=\"p\">})<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n\n<span class=\"w\">    <\/span><span class=\"nx\">apiConfigMapVolumeName<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;api-cm-volume&quot;<\/span>\n<span class=\"w\">    <\/span><span class=\"nx\">ttlSecondsAfterFinished<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"nb\">int32<\/span><span class=\"p\">(<\/span><span class=\"mi\">3600<\/span><span class=\"p\">)<\/span>\n\n<span class=\"w\">    <\/span><span class=\"nx\">jobToCreate<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"nx\">batchv1<\/span><span class=\"p\">.<\/span><span class=\"nx\">Job<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">ObjectMeta<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">metav1<\/span><span class=\"p\">.<\/span><span class=\"nx\">ObjectMeta<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">            <\/span><span class=\"nx\">Name<\/span><span class=\"p\">:<\/span><span class=\"w\">      <\/span><span class=\"nx\">jobname<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">            <\/span><span class=\"nx\">Namespace<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">namespace<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">Spec<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">batchv1<\/span><span class=\"p\">.<\/span><span class=\"nx\">JobSpec<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">         <\/span><span class=\"c1\">\/\/ We ensure that Jobs objects are garbaged collected after 1 hour<\/span>\n<span class=\"w\">            <\/span><span class=\"nx\">TTLSecondsAfterFinished<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">ttlSecondsAfterFinished<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">            <\/span><span class=\"nx\">Template<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">corev1<\/span><span class=\"p\">.<\/span><span class=\"nx\">PodTemplateSpec<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                <\/span><span class=\"nx\">Spec<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">corev1<\/span><span class=\"p\">.<\/span><span class=\"nx\">PodSpec<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">               <\/span><span class=\"c1\">\/\/ We don&#39;t want to restart the job if it fails<\/span>\n<span class=\"w\">                    <\/span><span class=\"nx\">RestartPolicy<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;Never&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">                    <\/span><span class=\"nx\">Containers<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">[]<\/span><span class=\"nx\">corev1<\/span><span class=\"p\">.<\/span><span class=\"nx\">Container<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                        <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                            <\/span><span class=\"nx\">Name<\/span><span class=\"p\">:<\/span><span class=\"w\">    <\/span><span class=\"nx\">jobname<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">                            <\/span><span class=\"nx\">Image<\/span><span class=\"p\">:<\/span><span class=\"w\">   <\/span><span class=\"s\">&quot;quay.io\/change-metrics\/monocle:1.8.0&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">                            <\/span><span class=\"nx\">Command<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">[]<\/span><span class=\"kt\">string<\/span><span class=\"p\">{<\/span><span class=\"s\">&quot;bash&quot;<\/span><span class=\"p\">},<\/span>\n<span class=\"w\">                            <\/span><span class=\"nx\">Args<\/span><span class=\"p\">:<\/span><span class=\"w\">    <\/span><span class=\"p\">[]<\/span><span class=\"kt\">string<\/span><span class=\"p\">{<\/span><span class=\"s\">&quot;-c&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;monocle janitor update-idents --elastic ${MONOCLE_ELASTIC_URL} --config \/etc\/monocle\/config.yaml&quot;<\/span><span class=\"p\">},<\/span>\n<span class=\"w\">                            <\/span><span class=\"nx\">Env<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">[]<\/span><span class=\"nx\">corev1<\/span><span class=\"p\">.<\/span><span class=\"nx\">EnvVar<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                                <\/span><span class=\"nx\">elasticUrlEnvVar<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">                            <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">                            <\/span><span class=\"nx\">VolumeMounts<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">[]<\/span><span class=\"nx\">corev1<\/span><span class=\"p\">.<\/span><span class=\"nx\">VolumeMount<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                                <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                                    <\/span><span class=\"nx\">Name<\/span><span class=\"p\">:<\/span><span class=\"w\">      <\/span><span class=\"nx\">apiConfigMapVolumeName<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">                                    <\/span><span class=\"nx\">ReadOnly<\/span><span class=\"p\">:<\/span><span class=\"w\">  <\/span><span class=\"kc\">true<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">                                    <\/span><span class=\"nx\">MountPath<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;\/etc\/monocle&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">                                <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">                            <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">                        <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">                    <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">                    <\/span><span class=\"nx\">Volumes<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">[]<\/span><span class=\"nx\">corev1<\/span><span class=\"p\">.<\/span><span class=\"nx\">Volume<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                        <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                            <\/span><span class=\"nx\">Name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiConfigMapVolumeName<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">                            <\/span><span class=\"nx\">VolumeSource<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">corev1<\/span><span class=\"p\">.<\/span><span class=\"nx\">VolumeSource<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                                <\/span><span class=\"nx\">ConfigMap<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">corev1<\/span><span class=\"p\">.<\/span><span class=\"nx\">ConfigMapVolumeSource<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                                    <\/span><span class=\"nx\">LocalObjectReference<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">corev1<\/span><span class=\"p\">.<\/span><span class=\"nx\">LocalObjectReference<\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                                        <\/span><span class=\"nx\">Name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nx\">apiConfigMapName<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">                                    <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">                                <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">                            <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">                        <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">                    <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">                <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">            <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">},<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"o\">:=<\/span><span class=\"w\"> <\/span><span class=\"nx\">ctrl_util<\/span><span class=\"p\">.<\/span><span class=\"nx\">SetControllerReference<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">instance<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">jobToCreate<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">r<\/span><span class=\"p\">.<\/span><span class=\"nx\">Scheme<\/span><span class=\"p\">);<\/span><span class=\"w\"> <\/span><span class=\"nx\">err<\/span><span class=\"w\"> <\/span><span class=\"o\">!=<\/span><span class=\"w\"> <\/span><span class=\"kc\">nil<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"nx\">logger<\/span><span class=\"p\">.<\/span><span class=\"nx\">Info<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Unable to set controller reference&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;name&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nx\">jobname<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n\n<span class=\"w\">    <\/span><span class=\"k\">return<\/span><span class=\"w\"> <\/span><span class=\"nx\">r<\/span><span class=\"p\">.<\/span><span class=\"nx\">Create<\/span><span class=\"p\">(<\/span><span class=\"nx\">ctx<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"nx\">jobToCreate<\/span><span class=\"p\">)<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<p>Key points here are:<\/p>\n<ul class=\"simple\">\n<li>We first check for an existing job (with the same name) and delete it\nif exists. This ensures that we only run one job at a time.<\/li>\n<li>We set a <a class=\"reference external\" href=\"https:\/\/kubernetes.io\/docs\/concepts\/workloads\/controllers\/job\/#ttl-mechanism-for-finished-jobs\">job TTL<\/a> to ensure that the Job Resource and its\ndecendents are deleted to avoid leftovers.<\/li>\n<\/ul>\n<p>To observe that behavior, just edit the <tt class=\"docutils literal\">config.yaml<\/tt> key of the\n<tt class=\"docutils literal\">ConfigMap<\/tt> to define a crawler's config in the <tt class=\"docutils literal\">demo<\/tt> <tt class=\"docutils literal\">workspace<\/tt>\nand see the job's logs.<\/p>\n<div class=\"highlight\"><pre><span><\/span>$<span class=\"w\"> <\/span>kubectl<span class=\"w\"> <\/span>get<span class=\"w\"> <\/span><span class=\"nb\">jobs<\/span>\nNAME<span class=\"w\">                <\/span>COMPLETIONS<span class=\"w\">   <\/span>DURATION<span class=\"w\">   <\/span>AGE\nupdate-idents-job<span class=\"w\">   <\/span><span class=\"m\">1<\/span>\/1<span class=\"w\">           <\/span>6s<span class=\"w\">         <\/span>21s\n$<span class=\"w\"> <\/span>kubectl<span class=\"w\"> <\/span>get<span class=\"w\"> <\/span>pods\nNAME<span class=\"w\">                                      <\/span>READY<span class=\"w\">   <\/span>STATUS<span class=\"w\">      <\/span>RESTARTS<span class=\"w\">   <\/span>AGE\nmonocle-sample-api-c75dcc789-gmwwm<span class=\"w\">        <\/span><span class=\"m\">1<\/span>\/1<span class=\"w\">     <\/span>Running<span class=\"w\">     <\/span><span class=\"m\">0<\/span><span class=\"w\">          <\/span>163m\nmonocle-sample-crawler-867888fb8c-95jgt<span class=\"w\">   <\/span><span class=\"m\">1<\/span>\/1<span class=\"w\">     <\/span>Running<span class=\"w\">     <\/span><span class=\"m\">0<\/span><span class=\"w\">          <\/span>163m\nmonocle-sample-elastic-0<span class=\"w\">                  <\/span><span class=\"m\">1<\/span>\/1<span class=\"w\">     <\/span>Running<span class=\"w\">     <\/span><span class=\"m\">0<\/span><span class=\"w\">          <\/span>3h1m\nupdate-idents-job-t7vgh<span class=\"w\">                   <\/span><span class=\"m\">0<\/span>\/1<span class=\"w\">     <\/span>Completed<span class=\"w\">   <\/span><span class=\"m\">0<\/span><span class=\"w\">          <\/span>9s\n$<span class=\"w\"> <\/span>kubectl<span class=\"w\"> <\/span>logs<span class=\"w\"> <\/span>update-idents-job-t7vgh\n<span class=\"m\">2023<\/span>-03-08<span class=\"w\"> <\/span><span class=\"m\">13<\/span>:57:52<span class=\"w\"> <\/span>INFO<span class=\"w\">    <\/span>Monocle.Backend.Janitor:48:<span class=\"w\"> <\/span>Janitor<span class=\"w\"> <\/span>will<span class=\"w\"> <\/span>process<span class=\"w\"> <\/span>changes<span class=\"w\"> <\/span>and<span class=\"w\"> <\/span>event<span class=\"w\"> <\/span><span class=\"o\">{<\/span><span class=\"s2\">&quot;workspace&quot;<\/span>:<span class=\"s2\">&quot;demo&quot;<\/span>,<span class=\"s2\">&quot;changes&quot;<\/span>:285,<span class=\"s2\">&quot;events&quot;<\/span>:8670<span class=\"o\">}<\/span>\n<span class=\"m\">2023<\/span>-03-08<span class=\"w\"> <\/span><span class=\"m\">13<\/span>:57:52<span class=\"w\"> <\/span>INFO<span class=\"w\">    <\/span>Monocle.Backend.Janitor:50:<span class=\"w\"> <\/span>Updated<span class=\"w\"> <\/span>changes<span class=\"w\"> <\/span><span class=\"o\">{<\/span><span class=\"s2\">&quot;count&quot;<\/span>:0<span class=\"o\">}<\/span>\n<span class=\"m\">2023<\/span>-03-08<span class=\"w\"> <\/span><span class=\"m\">13<\/span>:57:52<span class=\"w\"> <\/span>INFO<span class=\"w\">    <\/span>Monocle.Backend.Janitor:52:<span class=\"w\"> <\/span>Updated<span class=\"w\"> <\/span>events<span class=\"w\"> <\/span><span class=\"o\">{<\/span><span class=\"s2\">&quot;count&quot;<\/span>:0<span class=\"o\">}<\/span>\n<span class=\"m\">2023<\/span>-03-08<span class=\"w\"> <\/span><span class=\"m\">13<\/span>:57:52<span class=\"w\"> <\/span>INFO<span class=\"w\">    <\/span>Monocle.Backend.Janitor:54:<span class=\"w\"> <\/span>Author<span class=\"w\"> <\/span>cache<span class=\"w\"> <\/span>re-populated<span class=\"w\"> <\/span>with<span class=\"w\"> <\/span>entries<span class=\"w\"> <\/span><span class=\"o\">{<\/span><span class=\"s2\">&quot;count&quot;<\/span>:60<span class=\"o\">}<\/span>\n<\/pre><\/div>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"section\" id=\"how-to-generate-and-deploy-the-operator\">\n<h2>How to generate and deploy the operator<\/h2>\n<p>The Monocle project publishes and maintains the <a class=\"reference external\" href=\"https:\/\/quay.io\/repository\/change-metrics\/monocle-operator\">operator image<\/a> in his\nquay.io organisation and provides in the <tt class=\"docutils literal\">install<\/tt> directory two yaml\nfiles to install:<\/p>\n<ul class=\"simple\">\n<li>the CRDs: <tt class=\"docutils literal\">crd.yml<\/tt><\/li>\n<li>the required Resources defintion to install the operator:\n<tt class=\"docutils literal\">operator.yml<\/tt>.<\/li>\n<\/ul>\n<p>The installation is as simple as:<\/p>\n<div class=\"highlight\"><pre><span><\/span>$<span class=\"w\"> <\/span>kubectl<span class=\"w\"> <\/span>apply<span class=\"w\"> <\/span>-f<span class=\"w\"> <\/span>install\/crds.yml\n$<span class=\"w\"> <\/span>kubectl<span class=\"w\"> <\/span>apply<span class=\"w\"> <\/span>-f<span class=\"w\"> <\/span>install\/operator.yml\n<\/pre><\/div>\n<p>Both commands, above, require the <tt class=\"docutils literal\"><span class=\"pre\">cluster-admin<\/span><\/tt> role.<\/p>\n<p>The operator is installed into a dedicated namespace\n<tt class=\"docutils literal\"><span class=\"pre\">monocle-operator-system<\/span><\/tt>.<\/p>\n<p>The <tt class=\"docutils literal\">operator.yml<\/tt> takes care of creating:<\/p>\n<ul class=\"simple\">\n<li>the operator's <strong>Namespace<\/strong> <tt class=\"docutils literal\"><span class=\"pre\">monocle-operator-system<\/span><\/tt><\/li>\n<li>the <strong>Service Account<\/strong> <tt class=\"docutils literal\"><span class=\"pre\">monocle-operator-controller-manager<\/span><\/tt> into\nthe namespace<\/li>\n<li>the <strong>ClusterRole<\/strong> <tt class=\"docutils literal\"><span class=\"pre\">monocle-operator-manager-role<\/span><\/tt>. This role\ndefines <a class=\"reference external\" href=\"https:\/\/kubernetes.io\/docs\/reference\/access-authn-authz\/rbac\/#role-and-clusterrole\">authorizations<\/a> needed by the <tt class=\"docutils literal\">controller<\/tt> to act on the\ncluster's API. Authorizations are handled by the operator SDK through\nthe <a class=\"reference external\" href=\"https:\/\/book.kubebuilder.io\/reference\/markers\/rbac.html\">kubebuilder markers system<\/a>.<\/li>\n<li>the <strong>ClusterRole<\/strong> <tt class=\"docutils literal\"><span class=\"pre\">monocle-operator-monocle-editor-role<\/span><\/tt> which\ncan be assigned to a User to give authorisation to manipulate Monocle\ninstances (CR).<\/li>\n<li>the <strong>ClusterRoleBinding<\/strong> <tt class=\"docutils literal\"><span class=\"pre\">monocle-operator-manager-rolebinding<\/span><\/tt>\nthat allows the <tt class=\"docutils literal\"><span class=\"pre\">monocle-operator-controller-manager<\/span><\/tt> Service\nAccount to act upon the resources.<\/li>\n<li>the <strong>Deployment<\/strong> <tt class=\"docutils literal\"><span class=\"pre\">monocle-operator-controller-manager<\/span><\/tt> which runs\nthe operator's image.<\/li>\n<\/ul>\n<p>You can see if the deployment is successful by running the following\ncommand:<\/p>\n<div class=\"highlight\"><pre><span><\/span>$<span class=\"w\"> <\/span>kubectl<span class=\"w\"> <\/span>-n<span class=\"w\"> <\/span>monocle-operator-system<span class=\"w\"> <\/span>get<span class=\"w\"> <\/span>all\nNAME<span class=\"w\">                                                      <\/span>READY<span class=\"w\">   <\/span>STATUS<span class=\"w\">    <\/span>RESTARTS<span class=\"w\">   <\/span>AGE\npod\/monocle-operator-controller-manager-b999fdcc8-cxjkn<span class=\"w\">   <\/span><span class=\"m\">2<\/span>\/2<span class=\"w\">     <\/span>Running<span class=\"w\">   <\/span><span class=\"m\">0<\/span><span class=\"w\">          <\/span>32s\n\nNAME<span class=\"w\">                                                  <\/span>READY<span class=\"w\">   <\/span>UP-TO-DATE<span class=\"w\">   <\/span>AVAILABLE<span class=\"w\">   <\/span>AGE\ndeployment.apps\/monocle-operator-controller-manager<span class=\"w\">   <\/span><span class=\"m\">1<\/span>\/1<span class=\"w\">     <\/span><span class=\"m\">1<\/span><span class=\"w\">            <\/span><span class=\"m\">1<\/span><span class=\"w\">           <\/span>32s\n\nNAME<span class=\"w\">                                                            <\/span>DESIRED<span class=\"w\">   <\/span>CURRENT<span class=\"w\">   <\/span>READY<span class=\"w\">   <\/span>AGE\nreplicaset.apps\/monocle-operator-controller-manager-b999fdcc8<span class=\"w\">   <\/span><span class=\"m\">1<\/span><span class=\"w\">         <\/span><span class=\"m\">1<\/span><span class=\"w\">         <\/span><span class=\"m\">1<\/span><span class=\"w\">       <\/span>32s\n<\/pre><\/div>\n<p>and verify logs of the <tt class=\"docutils literal\"><span class=\"pre\">monocle-operator-controller-manager<\/span><\/tt> pod:<\/p>\n<div class=\"highlight\"><pre><span><\/span>$<span class=\"w\"> <\/span>kubectl<span class=\"w\"> <\/span>-n<span class=\"w\"> <\/span>monocle-operator-system<span class=\"w\"> <\/span>logs<span class=\"w\"> <\/span>deployment.apps\/monocle-operator-controller-manager\n...\n<span class=\"m\">1<\/span>.6784568095333292e+09<span class=\"w\">  <\/span>INFO<span class=\"w\">    <\/span>Starting<span class=\"w\"> <\/span>EventSource<span class=\"w\">    <\/span><span class=\"o\">{<\/span><span class=\"s2\">&quot;controller&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"s2\">&quot;monocle&quot;<\/span>,<span class=\"w\"> <\/span><span class=\"s2\">&quot;controllerGroup&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"s2\">&quot;monocle.monocle.change-metrics.io&quot;<\/span>,<span class=\"w\"> <\/span><span class=\"s2\">&quot;controllerKind&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"s2\">&quot;Monocle&quot;<\/span>,<span class=\"w\"> <\/span><span class=\"s2\">&quot;source&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"s2\">&quot;kind source: *v1.StatefulSet&quot;<\/span><span class=\"o\">}<\/span>\n<span class=\"m\">1<\/span>.678456809533342e+09<span class=\"w\">   <\/span>INFO<span class=\"w\">    <\/span>Starting<span class=\"w\"> <\/span>EventSource<span class=\"w\">    <\/span><span class=\"o\">{<\/span><span class=\"s2\">&quot;controller&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"s2\">&quot;monocle&quot;<\/span>,<span class=\"w\"> <\/span><span class=\"s2\">&quot;controllerGroup&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"s2\">&quot;monocle.monocle.change-metrics.io&quot;<\/span>,<span class=\"w\"> <\/span><span class=\"s2\">&quot;controllerKind&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"s2\">&quot;Monocle&quot;<\/span>,<span class=\"w\"> <\/span><span class=\"s2\">&quot;source&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"s2\">&quot;kind source: *v1.Service&quot;<\/span><span class=\"o\">}<\/span>\n<span class=\"m\">1<\/span>.6784568095333524e+09<span class=\"w\">  <\/span>INFO<span class=\"w\">    <\/span>Starting<span class=\"w\"> <\/span>Controller<span class=\"w\">     <\/span><span class=\"o\">{<\/span><span class=\"s2\">&quot;controller&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"s2\">&quot;monocle&quot;<\/span>,<span class=\"w\"> <\/span><span class=\"s2\">&quot;controllerGroup&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"s2\">&quot;monocle.monocle.change-metrics.io&quot;<\/span>,<span class=\"w\"> <\/span><span class=\"s2\">&quot;controllerKind&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"s2\">&quot;Monocle&quot;<\/span><span class=\"o\">}<\/span>\n<span class=\"m\">1<\/span>.678456809634908e+09<span class=\"w\">   <\/span>INFO<span class=\"w\">    <\/span>Starting<span class=\"w\"> <\/span>workers<span class=\"w\">        <\/span><span class=\"o\">{<\/span><span class=\"s2\">&quot;controller&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"s2\">&quot;monocle&quot;<\/span>,<span class=\"w\"> <\/span><span class=\"s2\">&quot;controllerGroup&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"s2\">&quot;monocle.monocle.change-metrics.io&quot;<\/span>,<span class=\"w\"> <\/span><span class=\"s2\">&quot;controllerKind&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"s2\">&quot;Monocle&quot;<\/span>,<span class=\"w\"> <\/span><span class=\"s2\">&quot;worker count&quot;<\/span>:<span class=\"w\"> <\/span><span class=\"m\">1<\/span><span class=\"o\">}<\/span>\n<\/pre><\/div>\n<div class=\"section\" id=\"how-to-start-a-monocle-instance\">\n<h3>How to start a Monocle instance<\/h3>\n<p>A Monocle instance can be reclaimed to the operator by applying the\n<tt class=\"docutils literal\">Sample<\/tt> resource:<\/p>\n<div class=\"highlight\"><pre><span><\/span>$<span class=\"w\"> <\/span>kubectl<span class=\"w\"> <\/span>apply<span class=\"w\"> <\/span>-f<span class=\"w\"> <\/span>config\/samples\/monocle_v1alpha1_monocle-alt.yaml\n$<span class=\"w\"> <\/span>kubectl<span class=\"w\"> <\/span>get<span class=\"w\"> <\/span>monocle<span class=\"w\"> <\/span>monocle-sample<span class=\"w\"> <\/span>-o<span class=\"w\"> <\/span>yaml\napiVersion:<span class=\"w\"> <\/span>monocle.monocle.change-metrics.io\/v1alpha1\nkind:<span class=\"w\"> <\/span>Monocle\nmetadata:\n<span class=\"w\">  <\/span>creationTimestamp:<span class=\"w\"> <\/span><span class=\"s2\">&quot;2023-03-10T14:20:24Z&quot;<\/span>\n<span class=\"w\">  <\/span>generation:<span class=\"w\"> <\/span><span class=\"m\">1<\/span>\n<span class=\"w\">  <\/span>labels:\n<span class=\"w\">    <\/span>app.kubernetes.io\/created-by:<span class=\"w\"> <\/span>monocle-operator\n<span class=\"w\">    <\/span>app.kubernetes.io\/instance:<span class=\"w\"> <\/span>monocle-sample\n<span class=\"w\">    <\/span>app.kubernetes.io\/managed-by:<span class=\"w\"> <\/span>kustomize\n<span class=\"w\">    <\/span>app.kubernetes.io\/name:<span class=\"w\"> <\/span>monocle\n<span class=\"w\">    <\/span>app.kubernetes.io\/part-of:<span class=\"w\"> <\/span>monocle-operator\n<span class=\"w\">  <\/span>name:<span class=\"w\"> <\/span>monocle-sample\n<span class=\"w\">  <\/span>namespace:<span class=\"w\"> <\/span>dev-admin\n<span class=\"w\">  <\/span>resourceVersion:<span class=\"w\"> <\/span><span class=\"s2\">&quot;326755&quot;<\/span>\n<span class=\"w\">  <\/span>uid:<span class=\"w\"> <\/span>4b72edc4-1192-4369-9348-2a669ae4d65d\nspec:\n<span class=\"w\">  <\/span>monoclePublicURL:<span class=\"w\"> <\/span>http:\/\/localhost:8090\nstatus:\n<span class=\"w\">  <\/span>monocle-api:<span class=\"w\"> <\/span>Ready\n<span class=\"w\">  <\/span>monocle-crawler:<span class=\"w\"> <\/span>Ready\n<span class=\"w\">  <\/span>monocle-elastic:<span class=\"w\"> <\/span>Ready\n<\/pre><\/div>\n<\/div>\n<div class=\"section\" id=\"how-to-generate-the-operator\">\n<h3>How to generate the operator<\/h3>\n<p>The operator is composed of:<\/p>\n<ul class=\"simple\">\n<li>the operator container image<\/li>\n<li>some Kubernetes Resources to enable its installation into a cluster<\/li>\n<\/ul>\n<p>The operator SDK provides the tooling to generate the operator image via\nthe Makefile:<\/p>\n<div class=\"highlight\"><pre><span><\/span>$<span class=\"w\"> <\/span>make<span class=\"w\"> <\/span>docker-build\n$<span class=\"w\"> <\/span><span class=\"c1\"># or<\/span>\n$<span class=\"w\"> <\/span>make<span class=\"w\"> <\/span>container-build\n<\/pre><\/div>\n<p>The generated image can be found locally and then can be pushed to the\nimage registry via:<\/p>\n<div class=\"highlight\"><pre><span><\/span>$<span class=\"w\"> <\/span>make<span class=\"w\"> <\/span>docker-push\n$<span class=\"w\"> <\/span><span class=\"c1\"># or<\/span>\n$<span class=\"w\"> <\/span>make<span class=\"w\"> <\/span>container-push\n<\/pre><\/div>\n<p>For Monocle we have created an additional <tt class=\"docutils literal\">Makefile<\/tt> target:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"c\"># Generate the install\/operator.yml and install\/crds.yml<\/span>\n<span class=\"nf\">.PHONY<\/span><span class=\"o\">:<\/span><span class=\"w\"> <\/span><span class=\"n\">gen<\/span>-<span class=\"n\">operator<\/span>-<span class=\"n\">install<\/span>\n<span class=\"nf\">gen-operator-install<\/span><span class=\"o\">:<\/span><span class=\"w\"> <\/span><span class=\"n\">manifests<\/span> <span class=\"n\">kustomize<\/span>\n<span class=\"w\">    <\/span><span class=\"nb\">cd<\/span><span class=\"w\"> <\/span>config\/manager<span class=\"w\"> <\/span><span class=\"o\">&amp;&amp;<\/span><span class=\"w\"> <\/span><span class=\"k\">$(<\/span>KUSTOMIZE<span class=\"k\">)<\/span><span class=\"w\"> <\/span>edit<span class=\"w\"> <\/span><span class=\"nb\">set<\/span><span class=\"w\"> <\/span>image<span class=\"w\"> <\/span><span class=\"nv\">controller<\/span><span class=\"o\">=<\/span><span class=\"si\">${<\/span><span class=\"nv\">IMG<\/span><span class=\"si\">}<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">$(<\/span>KUSTOMIZE<span class=\"k\">)<\/span><span class=\"w\"> <\/span>build<span class=\"w\"> <\/span>config\/operator<span class=\"w\"> <\/span>&gt;<span class=\"w\"> <\/span>install\/operator.yml\n<span class=\"w\">    <\/span><span class=\"k\">$(<\/span>KUSTOMIZE<span class=\"k\">)<\/span><span class=\"w\"> <\/span>build<span class=\"w\"> <\/span>config\/crd<span class=\"w\"> <\/span>&gt;<span class=\"w\"> <\/span>install\/crds.yml\n<\/pre><\/div>\n<p>This generates two <tt class=\"docutils literal\">manifests<\/tt> files needed to install the Monocle\noperator by relying on the <a class=\"reference external\" href=\"https:\/\/kubernetes.io\/docs\/tasks\/manage-kubernetes-objects\/kustomization\/\">kustomize<\/a> tool and provisionned (by the\noperator SDK) configs stored into <tt class=\"docutils literal\">\/config<\/tt>.<\/p>\n<\/div>\n<\/div>\n<div class=\"section\" id=\"to-conclude\">\n<h2>To conclude<\/h2>\n<p>It was our first attempt working with Kubernetes Operators, at first we\nwere astonished with the quantity of information and tools there are to\nstart developing an Operator like <a class=\"reference external\" href=\"https:\/\/kubebuilder.io\/\">kubebuiler<\/a> and <a class=\"reference external\" href=\"https:\/\/sdk.operatorframework.io\/\">operator SDK<\/a>.\nThen deciding which stack to use for the operator's development <a class=\"reference external\" href=\"https:\/\/sdk.operatorframework.io\/docs\/building-operators\/helm\/tutorial\/\">helm<\/a>,\n<a class=\"reference external\" href=\"https:\/\/sdk.operatorframework.io\/docs\/building-operators\/ansible\/tutorial\/\">ansible<\/a> or <a class=\"reference external\" href=\"https:\/\/sdk.operatorframework.io\/docs\/building-operators\/golang\/tutorial\/\">go<\/a>, and at the same time learning new things and\nfinding out how Kubernetes works in more detail every day.<\/p>\n<p>It was a great and challenging oportunity to learn this big new world of\nKubernetes, and we hope to have made the right choices for the\ncontinuity of Monocle Operator.<\/p>\n<p>Stay tuned for the next posts, where we will continue in this exciting\nnew world of Operators. Fell free to talk to us.<\/p>\n<p>Thank you hanging with us.<\/p>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2023 Feb 10 to Mar 01 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2023-feb-10-to-mar-01-summary.html","rel":"alternate"}},"published":"2023-03-01T10:00:00+00:00","updated":"2023-03-01T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2023-03-01:\/sprint-2023-feb-10-to-mar-01-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<div class=\"section\" id=\"sf-release-sf-3-8\">\n<h3>sf release (sf-3.8)<\/h3>\n<ul class=\"simple\">\n<li>We handled various sub-releases of SF 3.8 and finally announced the release <a class=\"reference external\" href=\"https:\/\/www.softwarefactory-project.io\/releases\/3.8\/\">https:\/\/www.softwarefactory-project.io\/releases\/3.8\/<\/a><\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"sf-operator-sf-4-0\">\n<h3>sf operator (sf-4.0)<\/h3>\n<ul class=\"simple\">\n<li>We updated SF Operator containers to mirror the SF \u2026<\/li><\/ul><\/div><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<div class=\"section\" id=\"sf-release-sf-3-8\">\n<h3>sf release (sf-3.8)<\/h3>\n<ul class=\"simple\">\n<li>We handled various sub-releases of SF 3.8 and finally announced the release <a class=\"reference external\" href=\"https:\/\/www.softwarefactory-project.io\/releases\/3.8\/\">https:\/\/www.softwarefactory-project.io\/releases\/3.8\/<\/a><\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"sf-operator-sf-4-0\">\n<h3>sf operator (sf-4.0)<\/h3>\n<ul class=\"simple\">\n<li>We updated SF Operator containers to mirror the SF 3.8 release<\/li>\n<li>We created a spec for sf-operator MVP<\/li>\n<\/ul>\n<\/div>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Hacking Zuul for developers - Running unit tests","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/hacking-zuul-for-developers-running-unit-tests.html","rel":"alternate"}},"published":"2023-02-09T00:00:00+00:00","updated":"2023-02-09T00:00:00+00:00","author":{"name":"Matthieu Huin"},"id":"tag:www.softwarefactory-project.io,2023-02-09:\/hacking-zuul-for-developers-running-unit-tests.html","summary":"<div class=\"admonition note\">\n<p class=\"first admonition-title\">Note<\/p>\n<p class=\"last\">This article was updated on Dec. 11, 2023 to change the suggested version of the Ubuntu VM.<\/p>\n<\/div>\n<p>This article is a followup on my previous post about <a class=\"reference external\" href=\"https:\/\/www.softwarefactory-project.io\/hacking-zuul-for-developers.html\">playing around with Zuul's source code<\/a> .\nHere I will explain how to set up an environment where you can run Zuul's unit \u2026<\/p>","content":"<div class=\"admonition note\">\n<p class=\"first admonition-title\">Note<\/p>\n<p class=\"last\">This article was updated on Dec. 11, 2023 to change the suggested version of the Ubuntu VM.<\/p>\n<\/div>\n<p>This article is a followup on my previous post about <a class=\"reference external\" href=\"https:\/\/www.softwarefactory-project.io\/hacking-zuul-for-developers.html\">playing around with Zuul's source code<\/a> .\nHere I will explain how to set up an environment where you can run Zuul's unit tests suite.<\/p>\n<div class=\"section\" id=\"requirements\">\n<h2>Requirements<\/h2>\n<p>The simplest way to set up this environment is to use a VM running Ubuntu 23.04 Server LTS or\nabove; or any OS where python 3.11+ is the default python interpreter.<\/p>\n<p>I will assume you have a way to spawn one such system, whether as a VM or something else,\nand that you have it configured in a way that you can SSH into it, and become root on it.<\/p>\n<p>I strongly advise you to deploy the &quot;beefiest&quot; server you can, with the amount of CPUs being\nthe most impactful parameter in terms of performances. As a point of reference, I am using\na VM with 8GB of RAM and 4 vCPUs, and I run the full test suite in slightly over 2 hours.<\/p>\n<\/div>\n<div class=\"section\" id=\"install-basic-tools\">\n<h2>Install basic tools<\/h2>\n<p>We're going to need a few things like git, pip and docker-compose to get everything up and running.<\/p>\n<pre class=\"code bash literal-block\">\nsudo<span class=\"w\"> <\/span>apt<span class=\"w\"> <\/span>-y<span class=\"w\"> <\/span>install<span class=\"w\"> <\/span>git<span class=\"w\"> <\/span>python3-pip<span class=\"w\"> <\/span>docker<span class=\"w\"> <\/span>docker-compose\n<\/pre>\n<p>Once we have pip, we'll use it to install <a class=\"reference external\" href=\"https:\/\/docs.opendev.org\/opendev\/bindep\/latest\/\">bindep<\/a>\nto figure out which dependencies are needed to run the tests, and <a class=\"reference external\" href=\"https:\/\/nox.thea.codes\/en\/stable\/\">nox<\/a>\nto actually run the test suite.<\/p>\n<pre class=\"code bash literal-block\">\nsudo<span class=\"w\"> <\/span>pip<span class=\"w\"> <\/span>install<span class=\"w\"> <\/span>nox<span class=\"w\"> <\/span>bindep\n<\/pre>\n<p>Note that I am setting up a VM so I am not too worried about messing up with the OS, but you might want\nto install these in user space rather than as root.<\/p>\n<\/div>\n<div class=\"section\" id=\"fetch-the-zuul-repo-and-install-test-dependencies\">\n<h2>Fetch the zuul repo and install test dependencies<\/h2>\n<p>If you don't have a copy of the repository somewhere already, let's fetch the source code:<\/p>\n<pre class=\"code bash literal-block\">\ngit<span class=\"w\"> <\/span>clone<span class=\"w\"> <\/span>--depth<span class=\"w\"> <\/span><span class=\"m\">1<\/span><span class=\"w\"> <\/span>https:\/\/opendev.org\/zuul\/zuul<span class=\"w\"> <\/span><span class=\"o\">&amp;&amp;<\/span><span class=\"w\"> <\/span><span class=\"nb\">cd<\/span><span class=\"w\"> <\/span>zuul\n<\/pre>\n<p>Bindep will next tell us what else we need to install:<\/p>\n<pre class=\"code bash literal-block\">\nsudo<span class=\"w\"> <\/span>apt<span class=\"w\"> <\/span>-y<span class=\"w\"> <\/span>install<span class=\"w\"> <\/span><span class=\"k\">$(<\/span>bindep<span class=\"w\"> <\/span>--brief<span class=\"w\"> <\/span><span class=\"nb\">test<\/span><span class=\"k\">)<\/span>\n<\/pre>\n<p>At this point bindep should install two database services: mysql and postgresql. We are going to\nset these up via containers, so we need to remove the packages. We do however want to make sure the DB clients\nare still installed.<\/p>\n<pre class=\"code bash literal-block\">\nsudo<span class=\"w\"> <\/span>apt<span class=\"w\"> <\/span>-y<span class=\"w\"> <\/span>remove<span class=\"w\"> <\/span>mysql-server<span class=\"w\"> <\/span>postgresql<span class=\"w\">\n<\/span>sudo<span class=\"w\"> <\/span>apt<span class=\"w\"> <\/span>-y<span class=\"w\"> <\/span>install<span class=\"w\"> <\/span>postgresql-client<span class=\"w\"> <\/span>mysql-client<span class=\"w\">\n<\/span>sudo<span class=\"w\"> <\/span>apt<span class=\"w\"> <\/span>-y<span class=\"w\"> <\/span>autoremove\n<\/pre>\n<\/div>\n<div class=\"section\" id=\"start-external-services\">\n<h2>Start external services<\/h2>\n<p>Zuul requires a database backend and a <a class=\"reference external\" href=\"https:\/\/zookeeper.apache.org\/\">Zookeeper<\/a> instance to be available,\neven when running the unit tests suite. Luckily for us, Zuul's developers team created a very handy script\nto deploy these services via a docker compose. Assuming you are still in the zuul directory:<\/p>\n<pre class=\"code bash literal-block\">\n<span class=\"nv\">ROOTCMD<\/span><span class=\"o\">=<\/span>sudo<span class=\"w\"> <\/span>tools\/test-setup-docker.sh\n<\/pre>\n<p>Once the script terminates, you should have two databases, certificates for Zookeeper and Zookeeper itself\nup and running, with parameters that can be used by the test suite. You can check the compose status with\n<cite>docker ps<\/cite> or check logs with <cite>docker-compose logs -f<\/cite>.<\/p>\n<p>If you are using a VM, it might be good to snapshot it now so you can easily get back to this state\nwhenever you want to run tests. Note that binary dependencies might change in the future so it might\nbe necessary to re-run bindep to keep up to date.<\/p>\n<\/div>\n<div class=\"section\" id=\"running-the-test-suite\">\n<h2>Running the test suite<\/h2>\n<p>Before anything else, we must ensure we can use as many file descriptors as we can, because the Zookeeper\nconnections require a lot of them.<\/p>\n<pre class=\"code bash literal-block\">\n<span class=\"nb\">ulimit<\/span><span class=\"w\"> <\/span>-n<span class=\"w\"> <\/span><span class=\"k\">$(<\/span><span class=\"nb\">ulimit<\/span><span class=\"w\"> <\/span>-Hn<span class=\"k\">)<\/span>\n<\/pre>\n<p>Once again, I am running a VM so I am not worried about breaking stuff, but you might want instead to\nuse a lower value than the hard limit provided by <cite>ulimit -Hn<\/cite>. What's for sure is that the default value,\n1024, is ridiculously low and needs to be increased.<\/p>\n<p>Also, note that this command will set the limit only for the current user session; don't forget to set it\nagain as needed.<\/p>\n<p>Assuming we are still in the zuul directory, we can list the different testing sessions configured for nox:<\/p>\n<pre class=\"code bash literal-block\">\nnox<span class=\"w\"> <\/span>-l\n<\/pre>\n<p>Let's do a dry run that will install python libraries requirements, but not run the actual tests:<\/p>\n<pre class=\"code bash literal-block\">\nnox<span class=\"w\"> <\/span>-s<span class=\"w\"> <\/span>tests<span class=\"w\"> <\/span>--install-only\n<\/pre>\n<p>This also will compile the React GUI application, which might take some time.<\/p>\n<p>We could have run the tests directly. But with this dry run, we can now install our own dependencies\nlike Zuul would with a Depends-On keyword in the commit message - except we do it manually.<\/p>\n<pre class=\"code bash literal-block\">\n<span class=\"nb\">source<\/span><span class=\"w\"> <\/span>.nox\/tests\/bin\/activate<span class=\"w\">\n<\/span><span class=\"nb\">cd<\/span><span class=\"w\"> <\/span>path\/to\/your\/dependency<span class=\"w\">\n<\/span>python<span class=\"w\"> <\/span>setup.py<span class=\"w\"> <\/span>install<span class=\"w\"> <\/span><span class=\"c1\"># or whatever you use to install the dependency<\/span>\n<\/pre>\n<p>To run the test suite with the modified virtualenv, use:<\/p>\n<pre class=\"code bash literal-block\">\nnox<span class=\"w\"> <\/span>-R<span class=\"w\"> <\/span>-s<span class=\"w\"> <\/span>tests\n<\/pre>\n<p>Drop the <cite>-R<\/cite> argument to recreate the virtualenv.<\/p>\n<p>Given that the test suite is pretty extensive, you may want to limit your run to a few tests at a time.\nYou can filter out which tests to run by matching a specific regex like\n<a class=\"reference external\" href=\"https:\/\/stestr.readthedocs.io\/en\/stable\/MANUAL.html#test-selection\">explained in the stestr documentation<\/a> .<\/p>\n<\/div>\n<div class=\"section\" id=\"conclusion\">\n<h2>Conclusion<\/h2>\n<p>This article presented a way to set up an environment where you can run Zuul's unit tests suite.\nI have compiled all the commands used here in a script in a <a class=\"reference external\" href=\"https:\/\/gist.github.com\/mhuin\/1177dc30971112404fd7c078651682ed\">gist<\/a>, if you want to automate things.<\/p>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2023 Jan 20 to Feb 08 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2023-jan-20-to-feb-08-summary.html","rel":"alternate"}},"published":"2023-02-08T10:00:00+00:00","updated":"2023-02-08T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2023-02-08:\/sprint-2023-jan-20-to-feb-08-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We added a feature for logscraper and logsender that will use configparser library to parse config file<\/li>\n<li>We fixed an issue in logscraper config that zuul_api_url was rendered incorrect<\/li>\n<li>We added a tenant quick selector to the GUI \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We added a feature for logscraper and logsender that will use configparser library to parse config file<\/li>\n<li>We fixed an issue in logscraper config that zuul_api_url was rendered incorrect<\/li>\n<li>We added a tenant quick selector to the GUI<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We are working on SF 3.8 release<\/li>\n<li>We read more about OpenShift and Operators<\/li>\n<li>We Updated some containers and pipelines for SF 3.8<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Reproducible Shell environments via Nix Flakes","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/reproducible-shell-environments-via-nix-flakes.html","rel":"alternate"}},"published":"2023-01-24T00:00:00+00:00","updated":"2023-01-24T00:00:00+00:00","author":{"name":"Fabien Boucher"},"id":"tag:www.softwarefactory-project.io,2023-01-24:\/reproducible-shell-environments-via-nix-flakes.html","summary":"<style type=\"text\/css\">\n\n  .literal {\n    border-radius: 6px;\n    padding: 1px 1px;\n    background-color: rgba(27,31,35,.05);\n  }\n\n<\/style><p>This post aims to help you getting started with Nix Flakes in order to\nease the distribution of reproducible shell environments.<\/p>\n<div class=\"section\" id=\"what-is-nix-flakes-1\">\n<span id=\"what-is-nix-flakes\"><\/span><h2>What is Nix Flakes ?<\/h2>\n<p>In a previous <a class=\"reference external\" href=\"https:\/\/www.softwarefactory-project.io\/howto-manage-shareable-reproducible-nix-environments-via-nix-shell.html\">blog post about nix-shell<\/a> we have introduced <a class=\"reference external\" href=\"https:\/\/nixos.org\/\">Nix<\/a> and\nhow \u2026<\/p><\/div>","content":"<style type=\"text\/css\">\n\n  .literal {\n    border-radius: 6px;\n    padding: 1px 1px;\n    background-color: rgba(27,31,35,.05);\n  }\n\n<\/style><p>This post aims to help you getting started with Nix Flakes in order to\nease the distribution of reproducible shell environments.<\/p>\n<div class=\"section\" id=\"what-is-nix-flakes-1\">\n<span id=\"what-is-nix-flakes\"><\/span><h2>What is Nix Flakes ?<\/h2>\n<p>In a previous <a class=\"reference external\" href=\"https:\/\/www.softwarefactory-project.io\/howto-manage-shareable-reproducible-nix-environments-via-nix-shell.html\">blog post about nix-shell<\/a> we have introduced <a class=\"reference external\" href=\"https:\/\/nixos.org\/\">Nix<\/a> and\nhow to benefit from the <tt class=\"docutils literal\"><span class=\"pre\">nix-shell<\/span><\/tt> feature to manage shareable and\nreproducible shell environments.<\/p>\n<p>However defining an environment using <tt class=\"docutils literal\"><span class=\"pre\">nix-shell<\/span><\/tt> lacks of\nstandardization. The new Nix <a class=\"reference external\" href=\"https:\/\/nixos.org\/manual\/nix\/stable\/command-ref\/new-cli\/nix3-flake.html\">flake<\/a> standardizes the usage of Nix\nartifacts. The Nix project provides a new command called <tt class=\"docutils literal\">nix flake<\/tt>\nwhich handles <tt class=\"docutils literal\">flake.nix<\/tt> files.<\/p>\n<\/div>\n<div class=\"section\" id=\"how-to-enable-nix-flake\">\n<h2>How to enable nix flake<\/h2>\n<p>To install Nix please refer to the <a class=\"reference external\" href=\"https:\/\/www.softwarefactory-project.io\/howto-manage-shareable-reproducible-nix-environments-via-nix-shell.html#how-to-install-nix\">previous blog post<\/a>.<\/p>\n<p>The <tt class=\"docutils literal\">flake<\/tt> feature is still considered experimental thus a specific\nNix configuration is necessary in <tt class=\"docutils literal\"><span class=\"pre\">~\/.config\/nix\/nix.conf<\/span><\/tt>:<\/p>\n<pre class=\"literal-block\">\nexperimental-features = nix-command flakes\n<\/pre>\n<\/div>\n<div class=\"section\" id=\"a-shell-environment-described-as-a-flake\">\n<h2>A Shell environment described as a Flake<\/h2>\n<p>Based on the <a class=\"reference external\" href=\"https:\/\/nixos.org\/manual\/nix\/stable\/command-ref\/new-cli\/nix3-flake.html#flake-format\">format definition for a flake<\/a> we can rewrite our\n<a class=\"reference external\" href=\"https:\/\/www.softwarefactory-project.io\/howto-manage-shareable-reproducible-nix-environments-via-nix-shell.html#a-simple-shell-nix-definition\">previous simple shell<\/a>.<\/p>\n<pre class=\"literal-block\">\n{\n  description = &quot;My-project build environment&quot;;\n  nixConfig.bash-prompt = &quot;[nix(my-project)] &quot;;\n  inputs = { nixpkgs.url = &quot;github:nixos\/nixpkgs\/22.11&quot;; };\n\n  outputs = { self, nixpkgs }:\n    let\n      pkgs = nixpkgs.legacyPackages.x86_64-linux.pkgs;\n      fooScript = pkgs.writeScriptBin &quot;foo.sh&quot; ''\n        #!\/bin\/sh\n        echo $FOO\n      '';\n    in {\n      devShells.x86_64-linux.default = pkgs.mkShell {\n        name = &quot;My-project build environment&quot;;\n        buildInputs = [\n          pkgs.python39\n          pkgs.python39Packages.tox\n          pkgs.python39Packages.flake8\n          pkgs.python39Packages.requests\n          pkgs.python39Packages.ipython\n          fooScript\n        ];\n        shellHook = ''\n          echo &quot;Welcome in $name&quot;\n          export FOO=&quot;BAR&quot;\n        '';\n      };\n    };\n}\n<\/pre>\n<p>Then by running <tt class=\"docutils literal\">nix develop<\/tt> we enter the shell (<tt class=\"docutils literal\">devShell<\/tt>).<\/p>\n<p>Note that, when working inside a Git repository, Nix expects that the\n<tt class=\"docutils literal\">flake.nix<\/tt> file is known by git (at least staged with\n<tt class=\"docutils literal\">git add flake.nix<\/tt>) or it will ignore it.<\/p>\n<p>The <tt class=\"docutils literal\">nix flake check<\/tt> command can be used to validate the flake file.<\/p>\n<pre class=\"literal-block\">\n$ nix develop\nWelcome in My-project-build-environment\n[nix(my-project)] python --version\nPython 3.9.15\n[nix(my-project)] which ipython\n\/nix\/store\/1kgkssy7lkgsxpjii618ddjq2v03473x-python3.9-ipython-8.4.0\/bin\/ipython\n<\/pre>\n<p>A <tt class=\"docutils literal\">flake.nix<\/tt> file must follow a specific format based on the <a class=\"reference external\" href=\"https:\/\/nixos.org\/guides\/nix-language.html\">Nix\nlanguage<\/a>. The base structure in an <tt class=\"docutils literal\">attribute set&nbsp; { ... }<\/tt> with\nspecific attributes such as:<\/p>\n<ul class=\"simple\">\n<li>description: a simple string that defines the flake's purpose.<\/li>\n<li>inputs: an attribute set that defines the flake's dependencies.<\/li>\n<li>outputs: a function that returns an attribute set with arbitratry\nattributes. However nix' subcommands expect to find specific\nattributes in the flake's output. For instance the <tt class=\"docutils literal\">nix develop<\/tt>\nexpects to find the <tt class=\"docutils literal\">devShells<\/tt> attribute.<\/li>\n<\/ul>\n<p>Note that we pin the <tt class=\"docutils literal\">nixpkgs<\/tt> version to the <tt class=\"docutils literal\">22.11<\/tt> tag by\noveriding the nixpkgs's url in the input attribute. For better\nreproducibility, nix creates a <tt class=\"docutils literal\">flake.lock<\/tt> file to pin dependencies\nto specific git hashes. This <tt class=\"docutils literal\">lock<\/tt> file should be distributed along\nwith the <tt class=\"docutils literal\">flake.nix<\/tt> file.<\/p>\n<p>The nix flake <tt class=\"docutils literal\">metadata<\/tt> and <tt class=\"docutils literal\">show<\/tt> subcommands can be used to\ndisplay flake' dependencies and output.<\/p>\n<p>A <tt class=\"docutils literal\">flake<\/tt> can be easily shared via a git repository. For instance the\n<a class=\"reference external\" href=\"https:\/\/github.com\/change-metrics\/monocle\">Monocle<\/a> project provides a flake with a <tt class=\"docutils literal\">devShell<\/tt> output then to\nget the same development environment than Monocle' developers, then\nsimply run:<\/p>\n<pre class=\"literal-block\">\n# Note that the first run might take long to fetch binary dependencies from the\n# nix cache and to build unavailable binary dependencies (from the cache).\n\n$ nix develop github:change-metrics\/monocle\n<\/pre>\n<\/div>\n<div class=\"section\" id=\"a-flake-to-build-the-software-factory-website\">\n<h2>A flake to build the Software Factory website<\/h2>\n<p>Our website requires some dependencies available on the system in order\nto be built. To ensure that each teams' member can build the website\nlocally, without spending time understanding which dependencies are\nneeded and then struggling with versions\/incompatibility issues, we\nprovide a <tt class=\"docutils literal\">flake<\/tt> file.<\/p>\n<p>Here is the <tt class=\"docutils literal\">flake.nix<\/tt> we are using:<\/p>\n<pre class=\"literal-block\">\n{\n  description = &quot;sf.io site builder flake&quot;;\n  inputs = { nixpkgs.url = &quot;github:nixos\/nixpkgs\/22.11&quot;; };\n\n  outputs = { self, nixpkgs }:\n    let\n      pkgs = nixpkgs.legacyPackages.x86_64-linux.pkgs;\n      buildScript = pkgs.writeScriptBin &quot;build-site.sh&quot; ''\n        #!\/bin\/sh\n\n        pushd src\n        .\/blog-htmx.sh\n        .\/blog-practical-haskell-use-cases.sh\n        .\/blog-introducing-effects.sh\n        .\/blog-introducing-functional-programming-to-pythonistas.sh\n        .\/blog-sf-resources-in-reason.sh\n        .\/blog-nix-shell.sh\n        .\/blog-nix-shell-flakes.sh\n        popd\n\n        pushd website\n        pelican content -o output\n        popd\n      '';\n    in {\n      devShells.x86_64-linux.default = pkgs.mkShell {\n        name = &quot;Website toolings shell&quot;;\n        buildInputs = [ pkgs.pandoc pkgs.python39Packages.pelican buildScript ];\n        shellHook = ''\n          echo &quot;Welcome in the nix shell for $name&quot;\n          echo &quot;Run the build-site.sh command to build the website in website\/output&quot;\n          echo &quot;Then run: firefox website\/output\/index.html&quot;\n        '';\n      };\n    };\n}\n<\/pre>\n<p>It is then really easy to build the website:<\/p>\n<pre class=\"literal-block\">\n$ nix develop\n$ build-site.sh\n<\/pre>\n<div class=\"section\" id=\"package-override\">\n<h3>Package override<\/h3>\n<p>If a specific package version is needed in the shell, then it is\npossible to override a package' attributes to make a new <a class=\"reference external\" href=\"https:\/\/nixos.org\/manual\/nix\/stable\/language\/derivations.html\">derivation<\/a>.\nFor instance, let's say that we need, for some reason, to stick to\n<tt class=\"docutils literal\">pelican<\/tt> version 4.7.2 instead of 4.8.0 version provided in\n<tt class=\"docutils literal\">nixpkgs<\/tt> 22.11. Then, we can override the <a class=\"reference external\" href=\"https:\/\/github.com\/NixOS\/nixpkgs\/blob\/22.11\/pkgs\/development\/python-modules\/pelican\/default.nix\">current definition<\/a> in\nour <tt class=\"docutils literal\">flake.nix<\/tt> using the <tt class=\"docutils literal\">overridePythonAttrs<\/tt> function this way:<\/p>\n<pre class=\"literal-block\">\nlet pelican = pkgs.python39Packages.pelican.overridePythonAttrs (old: rec {\n  version = &quot;4.7.2&quot;;\n  src = pkgs.fetchFromGitHub {\n    owner = &quot;getpelican&quot;;\n    repo = old.pname;\n    rev = &quot;refs\/tags\/${version}&quot;;\n    hash = &quot;sha256-ZBGzsyCtFt5uj9mpOpGdTzGJET0iwOAgDTy80P6anRU=&quot;;\n    postFetch = ''\n      rm -r $out\/pelican\/tests\/output\/custom_locale\/posts\n    '';\n  };\n});\n<\/pre>\n<p>and finally use the new <tt class=\"docutils literal\">pelican<\/tt> derivation in the <tt class=\"docutils literal\">buildInputs<\/tt> of\nthe <tt class=\"docutils literal\">mkShell<\/tt> function' attributes.<\/p>\n<p>Note that you might need to set <tt class=\"docutils literal\">hash<\/tt> to an empty string to force Nix\nto provide you the new hash to be set in the override.<\/p>\n<\/div>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2023 Dec 30 to Jan 18 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2023-dec-30-to-jan-18-summary.html","rel":"alternate"}},"published":"2023-01-18T10:00:00+00:00","updated":"2023-01-18T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2023-01-18:\/sprint-2023-dec-30-to-jan-18-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We fixed issue in logscraper, that it was not downloading important files from the logserver (zuul-info\/inventory.yaml), so the logsender was skipping the log directory<\/li>\n<li>We added a OpenSearch Dashboards script thas is base on the script \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We fixed issue in logscraper, that it was not downloading important files from the logserver (zuul-info\/inventory.yaml), so the logsender was skipping the log directory<\/li>\n<li>We added a OpenSearch Dashboards script thas is base on the script that we have in Software Factory project, but with few featues, for example: added support for AWS OpenSearch Dashboards<\/li>\n<li>We set tags permissions for openstack\/ci-log-processing project, so we will be ready to make a release after merging all content that is in review state<\/li>\n<li>We splitted variables related to the ca certificates for logscraper and logsender because they might not use same CA provider<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<div class=\"section\" id=\"next-sf-release-sf-3-8\">\n<h3>next sf release (sf-3.8)<\/h3>\n<ul class=\"simple\">\n<li>We removed all keycloak's conditionals and cauth mentions<\/li>\n<li>We fixed Gerrit container 3.5.4 reviewers-by-blame plugin and added zuul-results-summary plugin<\/li>\n<li>We fixed highlight Cgit feature<\/li>\n<li>We updated SF documentation for 3.8 release<\/li>\n<li>sf-docs : We removed an option from sfconfig command: provision-demo<\/li>\n<li>We set several containers to user a dedicated user instead of root<\/li>\n<li>We fixed some obsolete auth parameters being injected in sfconfig.yaml (remnants from previous upgrades sanitizing) when upgrading to 3.8<\/li>\n<li>We validated various patches and did some manual testing for the 3.8 release <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/etherpad\/p\/3.8_changes\">https:\/\/softwarefactory-project.io\/etherpad\/p\/3.8_changes<\/a><\/li>\n<li>We started the release process of 3.8 and got a candidate<\/li>\n<\/ul>\n<\/div>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Howto manage shareable, reproducible Nix environments via nix-shell","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/howto-manage-shareable-reproducible-nix-environments-via-nix-shell.html","rel":"alternate"}},"published":"2023-01-09T00:00:00+00:00","updated":"2023-01-09T00:00:00+00:00","author":{"name":"Fabien"},"id":"tag:www.softwarefactory-project.io,2023-01-09:\/howto-manage-shareable-reproducible-nix-environments-via-nix-shell.html","summary":"<style type=\"text\/css\">\n\n  .literal {\n    border-radius: 6px;\n    padding: 1px 1px;\n    background-color: rgba(27,31,35,.05);\n  }\n\n<\/style><p>This post aims to teach how to leverage nix via the nix-shell feature in\norder to ease the distribution of reproducible environment.<\/p>\n<div class=\"section\" id=\"what-is-nix-1\">\n<span id=\"what-is-nix\"><\/span><h2>What is Nix ?<\/h2>\n<p><a class=\"reference external\" href=\"https:\/\/nixos.org\">Nix<\/a> is a purely functional package manager. It manages packages\nindependently from your \u2026<\/p><\/div>","content":"<style type=\"text\/css\">\n\n  .literal {\n    border-radius: 6px;\n    padding: 1px 1px;\n    background-color: rgba(27,31,35,.05);\n  }\n\n<\/style><p>This post aims to teach how to leverage nix via the nix-shell feature in\norder to ease the distribution of reproducible environment.<\/p>\n<div class=\"section\" id=\"what-is-nix-1\">\n<span id=\"what-is-nix\"><\/span><h2>What is Nix ?<\/h2>\n<p><a class=\"reference external\" href=\"https:\/\/nixos.org\">Nix<\/a> is a purely functional package manager. It manages packages\nindependently from your system by maintaining a package store in\n<tt class=\"docutils literal\">\/nix\/store<\/tt>. This makes Nix convenient because various softwares and\nlibraries can be installed without the fear of breaking the base system\nprovided by your Linux distribution, nor having to handle potential\nconflicts in the versions of dependencies. Furthermore, as the Nix store\nis a graph of cryptographic hashes of package\u2019s build dependencies, then\nit brings the guarantee reproducible environments.<\/p>\n<\/div>\n<div class=\"section\" id=\"how-to-install-nix\">\n<h2>How to install nix<\/h2>\n<p>This page describes <a class=\"reference external\" href=\"https:\/\/nixos.org\/download.html#download-nix\">installation instructions<\/a>.<\/p>\n<p>We'll use the single user installation process (the user needs to be\nable to <tt class=\"docutils literal\">sudo <span class=\"pre\">-i<\/span><\/tt>):<\/p>\n<pre class=\"literal-block\">\nsh &lt;(curl -L https:\/\/nixos.org\/nix\/install) --no-daemon\n. ~\/.nix-profile\/etc\/profile.d\/nix.sh\n<\/pre>\n<p>Now let's verify our nix installation is working as expected:<\/p>\n<pre class=\"literal-block\">\nnix --version\nnix (Nix) 2.12.0\n<\/pre>\n<\/div>\n<div class=\"section\" id=\"using-the-nix-shell-to-setup-an-environment\">\n<h2>Using the nix-shell to setup an environment<\/h2>\n<p>A Nix shell environment gives access to specified packages.<\/p>\n<p>For instance, this command enhances the current shell environment to\nmake <tt class=\"docutils literal\">cowsay<\/tt> and <tt class=\"docutils literal\">fortune<\/tt> available in the PATH:<\/p>\n<pre class=\"literal-block\">\n$ nix-shell -p cowsay fortune\nthese 3 paths will be fetched (1.76 MiB download, 6.34 MiB unpacked):\n  \/nix\/store\/4agvv4d3jl9lcwxd46qjlkzcibsbryvz-recode-3.7.9\n  \/nix\/store\/fkrh0bzwymq0220fscz7grd3yrh5hzsd-cowsay-3.04\n  \/nix\/store\/k5dfq7qj0vp10jyb2pn780f323f4vdzm-fortune-mod-3.6.1\ncopying path '\/nix\/store\/fkrh0bzwymq0220fscz7grd3yrh5hzsd-cowsay-3.04' from 'https:\/\/cache.nixos.org'...\ncopying path '\/nix\/store\/4agvv4d3jl9lcwxd46qjlkzcibsbryvz-recode-3.7.9' from 'https:\/\/cache.nixos.org'...\ncopying path '\/nix\/store\/k5dfq7qj0vp10jyb2pn780f323f4vdzm-fortune-mod-3.6.1' from 'https:\/\/cache.nixos.org'...\n\n$ type cowsay fortune\ncowsay is hashed (\/nix\/store\/fkrh0bzwymq0220fscz7grd3yrh5hzsd-cowsay-3.04\/bin\/cowsay)\nfortune is hashed (\/nix\/store\/k5dfq7qj0vp10jyb2pn780f323f4vdzm-fortune-mod-3.6.1\/bin\/fortune)\n<\/pre>\n<p>The Nix project maintains a binary cache then packages are usually just\ndownloaded from the cache.<\/p>\n<p>However this command does not guarantee the same versions of packages\nwill be installed when the same command runs on another machine. Indeed\npackage definitions are maintained in the <a class=\"reference external\" href=\"https:\/\/github.com\/NixOS\/nixpkgs\">nixpkgs<\/a> project, and to\nensure reproducibility the version of nixpkgs must be pinned.<\/p>\n<p>By default, running <tt class=\"docutils literal\"><span class=\"pre\">nix-shell<\/span><\/tt>, uses the default nixpkgs channel,\nwhich might be set to a different version across nix installations.<\/p>\n<pre class=\"literal-block\">\n$ nix-instantiate --eval -E '(import &lt;nixpkgs&gt; {}).lib.version'\n&quot;23.05pre440754.0c9aadc8eff&quot;\n<\/pre>\n<p>The <tt class=\"docutils literal\"><span class=\"pre\">nix-shell<\/span><\/tt> command can be run with a pinned version of nixpkgs,\nby doing so we get the guarantee run a reproducible shell environment:<\/p>\n<pre class=\"literal-block\">\n$ nix-shell -I nixpkgs=https:\/\/github.com\/NixOS\/nixpkgs\/archive\/refs\/tags\/22.11.tar.gz -p cowsay\n<\/pre>\n<p>Now let use our new knowledge to get a Python 3.9 shell with various\nPython libraries and ipython:<\/p>\n<pre class=\"literal-block\">\n$ nix-shell -I nixpkgs=https:\/\/github.com\/NixOS\/nixpkgs\/archive\/refs\/tags\/22.11.tar.gz -p \\\npython39 python39Packages.tox python39Packages.flake8 python39Packages.requests \\\npython39Packages.ipython\n\n$ type python tox flake8 ipython\npython is \/nix\/store\/h4h5rxs0hzpzvz37yrwv1k2na1acgzww-python3-3.9.15\/bin\/python\ntox is hashed (\/nix\/store\/0iifww8anqsg84apj0dklrpiqjwn1nzy-python3.9-tox-3.27.1\/bin\/tox)\nflake8 is hashed (\/nix\/store\/mri6xdgqa5b4hj7by88mlidksi1h7kd2-python3.9-flake8-5.0.4\/bin\/flake8)\nipython is \/nix\/store\/1kgkssy7lkgsxpjii618ddjq2v03473x-python3.9-ipython-8.4.0\/bin\/ipython\n\n$ python --version &amp;&amp; flake8 --version &amp;&amp; tox --version &amp;&amp; ipython --version\nPython 3.9.15\n5.0.4 (mccabe: 0.7.0, pycodestyle: 2.9.1, pyflakes: 2.5.0) CPython 3.9.15 on Linux\n3.27.1 imported from \/nix\/store\/0iifww8anqsg84apj0dklrpiqjwn1nzy-python3.9-tox-3.27.1\/lib\/python3.9\/site-packages\/tox\/__init__.py\n8.4.0\n\n$ exit\n\n# Note that running again the nix-shell command will enter the shell instantanously as all\n# binaries have been fetched into \/nix\/store already.\n<\/pre>\n<p>If you try the same commands as above on your machine you should see the\nextact same output.<\/p>\n<p>Currently, nixpkgs owns definitions for around 80,000 packages. You can\nsearch for available packages on <a class=\"reference external\" href=\"https:\/\/search.nixos.org\">search.nixos.org<\/a>.<\/p>\n<\/div>\n<div class=\"section\" id=\"a-simple-shell-nix-definition\">\n<span id=\"a-simple-shellnix-definition\"><\/span><h2>A simple shell.nix definition<\/h2>\n<p>The <tt class=\"docutils literal\"><span class=\"pre\">nix-shell<\/span><\/tt> command looks for a <tt class=\"docutils literal\">shell.nix<\/tt> file in the current\ndirectory and if it exists the shell environment is loaded. This is\nhandy in order to share with co-workers a common and reproducible work\nenvironment for a given project. Since it is a pure text file, it can\nalso be easily versioned with git.<\/p>\n<p>As the most simple example of <tt class=\"docutils literal\">shell.nix<\/tt> to deploy the previous\nPython environment:<\/p>\n<pre class=\"literal-block\">\n{ pkgs ? import (fetchTarball &quot;https:\/\/github.com\/NixOS\/nixpkgs\/archive\/refs\/tags\/22.11.tar.gz&quot;) {} }:\n\nlet fooScript = pkgs.writeScriptBin &quot;foo.sh&quot; ''\n  #!\/bin\/sh\n  echo $FOO\n'';\n\nin pkgs.mkShell {\n  name = &quot;My-project build environment&quot;;\n  buildInputs = [\n    pkgs.python39\n    pkgs.python39Packages.tox\n    pkgs.python39Packages.flake8\n    pkgs.python39Packages.requests\n    pkgs.python39Packages.ipython\n    fooScript\n  ];\n  shellHook = ''\n    echo &quot;Welcome in $name&quot;\n    export FOO=&quot;BAR&quot;\n  '';\n}\n<\/pre>\n<p>This <tt class=\"docutils literal\">shell.nix<\/tt> sample describes a shell with:<\/p>\n<ul class=\"simple\">\n<li>Some Python packages available<\/li>\n<li>A script <tt class=\"docutils literal\">foo.sh<\/tt> available in the PATH<\/li>\n<li>Some commands (via <tt class=\"docutils literal\">shellHook<\/tt> to run a shell startup)<\/li>\n<\/ul>\n<p>Enter the shell by typing: <tt class=\"docutils literal\"><span class=\"pre\">nix-shell<\/span><\/tt>.<\/p>\n<\/div>\n<div class=\"section\" id=\"to-go-further\">\n<h2>To go further<\/h2>\n<p>In this post we learned the basic steps to bootstrap a simple shell\nenvironment with Nix. However more complex and reproducible environment\nsetups can be built via a Nix shell, like the setup of services\n(MariaDB, Zookeeper, ...), installation of additional scripts,\ncompilation\/installation of softwares and libraries not available in\nnixpkgs, but this goes beyond that simple introdution.<\/p>\n<p>Here are some interesting resources to <a class=\"reference external\" href=\"https:\/\/nix.dev\/recommended-reading\">continue your learning<\/a>.<\/p>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Getting started with CodeReady Containers","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/getting-started-with-codeready-containers.html","rel":"alternate"}},"published":"2022-12-22T00:00:00+00:00","updated":"2022-12-22T00:00:00+00:00","author":{"name":"dpawlik"},"id":"tag:www.softwarefactory-project.io,2022-12-22:\/getting-started-with-codeready-containers.html","summary":"<div class=\"section\" id=\"crc-codeready-containers\">\n<h2>CRC - CodeReady Containers<\/h2>\n<div class=\"section\" id=\"what-is-crc\">\n<h3>What is CRC?<\/h3>\n<p>The CRC (Red Hat CodeReady Containers) is a solution to deploy OpenShift\ncluster on your local machine in minutes.\nRed Hat OpenShift provides a complete solution that includes a stable Kubernetes\nengine with robust security and many integrated capabilities required to\noperate a complete \u2026<\/p><\/div><\/div>","content":"<div class=\"section\" id=\"crc-codeready-containers\">\n<h2>CRC - CodeReady Containers<\/h2>\n<div class=\"section\" id=\"what-is-crc\">\n<h3>What is CRC?<\/h3>\n<p>The CRC (Red Hat CodeReady Containers) is a solution to deploy OpenShift\ncluster on your local machine in minutes.\nRed Hat OpenShift provides a complete solution that includes a stable Kubernetes\nengine with robust security and many integrated capabilities required to\noperate a complete application platform. It comes in several\neditions including as a fully managed public cloud service or\nself-managed on infrastructure across datacenters, public clouds, and edge. [ <a class=\"reference external\" href=\"https:\/\/www.redhat.com\/en\/technologies\/cloud-computing\/openshift\/red-hat-openshift-kubernetes#benefits\">source<\/a> ]<\/p>\n<p>That project is very resource-hungry, because it deploys a dedicated\nlibvirt instance (virtual machine), configures network, deploys Kubernetes inside\nthe instance (VM) and on the end deploys OpenShift with operators.\nAll new pods that would be spawned later by <cite>sf-operator<\/cite> would be running inside\nthat VM, that's why the minimum of our configuration to deploy CRC and <cite>sf-operator<\/cite>\ntook 14 GB of RAM, 6 vcpus and 60 GB of HDD.<\/p>\n<\/div>\n<div class=\"section\" id=\"few-words-about-sf-operator\">\n<h3>Few words about SF Operator<\/h3>\n<p>The <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/plugins\/gitiles\/software-factory\/sf-operator\">SF Operator<\/a> project\nis most likely a new solution that the Software Factory Project will use.\nThe <a class=\"reference external\" href=\"https:\/\/kubernetes.io\/docs\/concepts\/extend-kubernetes\/operator\/\">operators<\/a> are software\nextensions to Kubernetes that make use of custom resources to manage applications and their components.\nOperators follow Kubernetes principles, notably the control loop.<\/p>\n<\/div>\n<div class=\"section\" id=\"why-are-we-using-it\">\n<h3>Why are we using it?<\/h3>\n<p>The SF Operator project is already tested on a vanilla Kubernetes deployment, however\nwe cannot assume that all of Software Factory Project users are using it.\nThe future Software Factory release that would be based on Kubernetes deployment\nshould be also tested on different platform. That's why we aim to\ncreate an universal operator, that would be possible to deploy on many\nKubernetes base clusters.<\/p>\n<\/div>\n<div class=\"section\" id=\"how-to-setup-crc\">\n<h3>How to setup CRC?<\/h3>\n<p>The CRC deployment is easy to deploy. The CRC community has simple <a class=\"reference external\" href=\"https:\/\/crc.dev\/crc\/\">documentation<\/a>.\nAlso the OpenStack community creates its own repository where they describe\nhow to setup the environment. More information you can find <a class=\"reference external\" href=\"https:\/\/github.com\/openstack-k8s-operators\/install_yamls\/tree\/master\/devsetup#crc-automation--tool-deployment\">here<\/a>.<\/p>\n<p>The Software Factory Project uses the crc Ansible role, which\nyou can find in <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/plugins\/gitiles\/software-factory\/sf-infra\">sf-infra<\/a> repository.<\/p>\n<p>Here is a playbook that deploy a crc cluster using the crc role we maintain:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">hosts<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">crc.dev<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">vars<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">crc_debug<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">true<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">nested_virtualization<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">false<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">openshift_pull_secret<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p p-Indicator\">|<\/span>\n<span class=\"w\">      <\/span><span class=\"no\">&lt; ADD YOUR PULL-SECRET.TXT HERE&gt;<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">pre_tasks<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">    <\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">Ensure CentOS runs with selinux permissive<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">become<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">true<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">selinux<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">policy<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">targeted<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">state<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">permissive<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">roles<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">    <\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">extra\/crc<\/span>\n<\/pre><\/div>\n<p>where the pull-secret.txt can be generated <a class=\"reference external\" href=\"https:\/\/cloud.redhat.com\/openshift\/create\/local\">here<\/a>.<\/p>\n<\/div>\n<div class=\"section\" id=\"ingress-how-it-s-done-for-testing-purpose\">\n<h3>Ingress - how it's done for testing purpose?<\/h3>\n<p>Kubernetes Ingress is an API object that provides routing rules to manage\nexternal users' access to the services in a Kubernetes cluster,\ntypically via HTTPS\/HTTP.\nWith Ingress, you can easily set up rules for routing traffic without creating\na bunch of Load Balancers or exposing each service on the node. [ <a class=\"reference external\" href=\"https:\/\/www.ibm.com\/cloud\/blog\/kubernetes-ingress\">source<\/a> ]<\/p>\n<p>In short, ingress exposes HTTP and HTTPS routes from outside the\ncluster to services within the cluster. Traffic routing is controlled by\nrules defined on the Ingress resource. [ <a class=\"reference external\" href=\"https:\/\/kubernetes.io\/docs\/concepts\/services-networking\/ingress\/#what-is-ingress\">source<\/a> ]<\/p>\n<p>On Kubernetes deployment created with <a class=\"reference external\" href=\"https:\/\/kind.sigs.k8s.io\/\">Kind<\/a> tool,\nto forward ports from the host to an ingress controller running on the node, it just\nrequires to add a dedicated <a class=\"reference external\" href=\"https:\/\/kind.sigs.k8s.io\/docs\/user\/ingress\/#setting-up-an-ingress-controller\">extraPortMapping<\/a>\nparameter into the configuration file.<\/p>\n<p>By default, the VM L0 (the VM where you are deploying CRC), creates a new\nnetwork that is also routed on that VM. In most cases, the ip address of the\ncrc services are bound to <cite>192.168.130.11<\/cite>.\nIt means, that to communicate with the services such as Openshift Web Console\nor sf-operator deployed services, it requires to:<\/p>\n<ul class=\"simple\">\n<li>add security group rules to your instance (if you are deploying CRC in Cloud Provider VM),<\/li>\n<li>setup HAProxy that will redirect queries to the services working in CRC network.<\/li>\n<\/ul>\n<p>How to add the security group rules should be described in your Cloud Provider\ndocumentation, so I will skip that step.<\/p>\n<\/div>\n<div class=\"section\" id=\"how-to-enable-crc-console-by-using-haproxy\">\n<h3>How to enable CRC Console by using HAProxy<\/h3>\n<p>The manual is based on blog <a class=\"reference external\" href=\"https:\/\/nerc-project.github.io\/nerc-docs\/other-tools\/kubernetes\/crc\/#using-crc-web-interface\">post<\/a>.\nHow to enable:<\/p>\n<ul class=\"simple\">\n<li>install required services<\/li>\n<\/ul>\n<div class=\"highlight\"><pre><span><\/span>sudo<span class=\"w\"> <\/span>dnf<span class=\"w\"> <\/span>install<span class=\"w\"> <\/span>-y<span class=\"w\"> <\/span>haproxy<span class=\"w\"> <\/span>policycoreutils-python-utils\n<\/pre><\/div>\n<ul class=\"simple\">\n<li>configure environment variables<\/li>\n<\/ul>\n<div class=\"highlight\"><pre><span><\/span><span class=\"nb\">export<\/span><span class=\"w\"> <\/span><span class=\"nv\">SERVER_IP<\/span><span class=\"o\">=<\/span><span class=\"k\">$(<\/span>hostname<span class=\"w\"> <\/span>--ip-address<span class=\"w\"> <\/span><span class=\"p\">|<\/span>cut<span class=\"w\"> <\/span>-d<span class=\"se\">\\ <\/span><span class=\"w\"> <\/span>-f3<span class=\"k\">)<\/span>\n<span class=\"nb\">export<\/span><span class=\"w\"> <\/span><span class=\"nv\">CRC_IP<\/span><span class=\"o\">=<\/span><span class=\"k\">$(<\/span>crc<span class=\"w\"> <\/span>ip<span class=\"k\">)<\/span>\n<\/pre><\/div>\n<ul class=\"simple\">\n<li>create HAProxy configuration<\/li>\n<\/ul>\n<div class=\"highlight\"><pre><span><\/span>cat<span class=\"w\"> <\/span><span class=\"s\">&lt;&lt;EOF | sudo tee \/etc\/haproxy\/haproxy.cfg<\/span>\n<span class=\"s\">global<\/span>\n\n<span class=\"s\">defaults<\/span>\n<span class=\"s\">log global<\/span>\n<span class=\"s\">mode http<\/span>\n<span class=\"s\">timeout connect 0<\/span>\n<span class=\"s\">timeout client 0<\/span>\n<span class=\"s\">timeout server 0<\/span>\n\n<span class=\"s\">frontend apps<\/span>\n<span class=\"s\">bind ${SERVER_IP}:80<\/span>\n<span class=\"s\">bind ${SERVER_IP}:443<\/span>\n<span class=\"s\">option tcplog<\/span>\n<span class=\"s\">mode tcp<\/span>\n<span class=\"s\">default_backend apps<\/span>\n\n<span class=\"s\">backend apps<\/span>\n<span class=\"s\">mode tcp<\/span>\n<span class=\"s\">balance roundrobin<\/span>\n<span class=\"s\">option ssl-hello-chk<\/span>\n<span class=\"s\">server webserver1 ${CRC_IP}:443 check<\/span>\n\n<span class=\"s\">frontend api<\/span>\n<span class=\"s\">bind ${SERVER_IP}:6443<\/span>\n<span class=\"s\">option tcplog<\/span>\n<span class=\"s\">mode tcp<\/span>\n<span class=\"s\">default_backend api<\/span>\n\n<span class=\"s\">backend api<\/span>\n<span class=\"s\">mode tcp<\/span>\n<span class=\"s\">balance roundrobin<\/span>\n<span class=\"s\">option ssl-hello-chk<\/span>\n<span class=\"s\">server webserver1 ${CRC_IP}:6443 check<\/span>\n<span class=\"s\">EOF<\/span>\n<\/pre><\/div>\n<ul class=\"simple\">\n<li>add SELinux policy (if you did not set SELinux to permissive)<\/li>\n<\/ul>\n<div class=\"highlight\"><pre><span><\/span>sudo<span class=\"w\"> <\/span>semanage<span class=\"w\"> <\/span>port<span class=\"w\"> <\/span>-a<span class=\"w\"> <\/span>-t<span class=\"w\"> <\/span>http_port_t<span class=\"w\"> <\/span>-p<span class=\"w\"> <\/span>tcp<span class=\"w\"> <\/span><span class=\"m\">6443<\/span>\n<\/pre><\/div>\n<ul class=\"simple\">\n<li>start the service<\/li>\n<\/ul>\n<div class=\"highlight\"><pre><span><\/span>sudo<span class=\"w\"> <\/span>systemctl<span class=\"w\"> <\/span>start<span class=\"w\"> <\/span>haproxy\nsudo<span class=\"w\"> <\/span>systemctl<span class=\"w\"> <\/span><span class=\"nb\">enable<\/span><span class=\"w\"> <\/span>haproxy\n<\/pre><\/div>\n<ul class=\"simple\">\n<li>optionally, generate the \/etc\/hosts entries (execute that on crc host, but add into your local VM)<\/li>\n<\/ul>\n<div class=\"highlight\"><pre><span><\/span><span class=\"nb\">echo<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;<\/span><span class=\"k\">$(<\/span>ip<span class=\"w\"> <\/span>route<span class=\"w\"> <\/span>get<span class=\"w\"> <\/span><span class=\"m\">1<\/span>.2.3.4<span class=\"w\"> <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span>awk<span class=\"w\"> <\/span><span class=\"s1\">&#39;{print $7}&#39;<\/span><span class=\"w\"> <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span>tr<span class=\"w\"> <\/span>-d<span class=\"w\"> <\/span><span class=\"s1\">&#39;\\n&#39;<\/span><span class=\"k\">)<\/span><span class=\"s2\"> console-openshift-console.apps-crc.testing api.crc.testing canary-openshift-ingress-canary.apps-crc.testing default-route-openshift-image-registry.apps-crc.testing downloads-openshift-console.apps-crc.testing oauth-openshift.apps-crc.testing apps-crc.testing&quot;<\/span><span class=\"w\"> <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span>sudo<span class=\"w\"> <\/span>tee<span class=\"w\"> <\/span>-a<span class=\"w\"> <\/span>\/etc\/hosts\n<\/pre><\/div>\n<p>Above steps are automatically done by Ansible due it has been included in\n<cite>extra\/crc<\/cite> role in <cite>sf-infra<\/cite> project.<\/p>\n<p>After applying that, the OpenShift WebUI console should be available on\n<cite>https:\/\/console-openshift-console.apps-crc.testing\/<\/cite>.<\/p>\n<img alt=\"loginpage\" src=\"images\/crc-1.jpg\" \/>\n<div class=\"line-block\">\n<div class=\"line\"><br \/><\/div>\n<\/div>\n<img alt=\"overview\" src=\"images\/crc-2.jpg\" \/>\n<div class=\"line-block\">\n<div class=\"line\"><br \/><\/div>\n<\/div>\n<img alt=\"overview_cont\" src=\"images\/crc-3.jpg\" \/>\n<div class=\"line-block\">\n<div class=\"line\"><br \/><\/div>\n<\/div>\n<img alt=\"pods\" src=\"images\/crc-4.jpg\" \/>\n<div class=\"line-block\">\n<div class=\"line\"><br \/><\/div>\n<\/div>\n<img alt=\"pv\" src=\"images\/crc-5.jpg\" \/>\n<div class=\"line-block\">\n<div class=\"line\"><br \/><\/div>\n<\/div>\n<img alt=\"nodes\" src=\"images\/crc-6.jpg\" \/>\n<\/div>\n<\/div>\n<div class=\"section\" id=\"remove-crc-pull-secret-txt-from-the-cluster\">\n<h2>Remove CRC pull-secret.txt from the cluster<\/h2>\n<p>If you would like to make a snapshot of the CRC VM and remove sensitive\ncontent from the cluster, it is required to perform an action:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"c1\"># From https:\/\/github.com\/crc-org\/snc\/blob\/master\/snc.sh#L241<\/span>\n<span class=\"nv\">mc_before_removing_pullsecret<\/span><span class=\"o\">=<\/span><span class=\"k\">$(<\/span>\/usr\/local\/bin\/oc<span class=\"w\"> <\/span>get<span class=\"w\"> <\/span>mc<span class=\"w\"> <\/span>--sort-by<span class=\"o\">=<\/span>.metadata.creationTimestamp<span class=\"w\"> <\/span>--no-headers<span class=\"w\"> <\/span>-oname<span class=\"k\">)<\/span>\n\/usr\/local\/bin\/oc<span class=\"w\"> <\/span>replace<span class=\"w\"> <\/span>-f<span class=\"w\"> <\/span>https:\/\/raw.githubusercontent.com\/crc-org\/snc\/master\/pull-secret.yaml\n<span class=\"nv\">mc_after_removing_pullsecret<\/span><span class=\"o\">=<\/span><span class=\"k\">$(<\/span>\/usr\/local\/bin\/oc<span class=\"w\"> <\/span>get<span class=\"w\"> <\/span>mc<span class=\"w\"> <\/span>--sort-by<span class=\"o\">=<\/span>.metadata.creationTimestamp<span class=\"w\"> <\/span>--no-headers<span class=\"w\"> <\/span>-oname<span class=\"k\">)<\/span>\n<span class=\"k\">while<\/span><span class=\"w\"> <\/span><span class=\"o\">[<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;<\/span><span class=\"si\">${<\/span><span class=\"nv\">mc_before_removing_pullsecret<\/span><span class=\"si\">}<\/span><span class=\"s2\">&quot;<\/span><span class=\"w\"> <\/span><span class=\"o\">==<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;<\/span><span class=\"si\">${<\/span><span class=\"nv\">mc_after_removing_pullsecret<\/span><span class=\"si\">}<\/span><span class=\"s2\">&quot;<\/span><span class=\"w\"> <\/span><span class=\"o\">]<\/span><span class=\"p\">;<\/span><span class=\"w\"> <\/span><span class=\"k\">do<\/span>\n<span class=\"w\">    <\/span><span class=\"nb\">echo<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Machine config is still not rendered&quot;<\/span>\n<span class=\"w\">    <\/span><span class=\"nv\">mc_after_removing_pullsecret<\/span><span class=\"o\">=<\/span><span class=\"k\">$(<\/span>\/usr\/local\/bin\/oc<span class=\"w\"> <\/span>get<span class=\"w\"> <\/span>mc<span class=\"w\"> <\/span>--sort-by<span class=\"o\">=<\/span>.metadata.creationTimestamp<span class=\"w\"> <\/span>--no-headers<span class=\"w\"> <\/span>-oname<span class=\"k\">)<\/span>\n<span class=\"k\">done<\/span>\n<\/pre><\/div>\n<div class=\"section\" id=\"the-local-path-provisioner\">\n<h3>The local-path-provisioner<\/h3>\n<p>Local Path Provisioner provides a way for the Kubernetes users to utilize\nthe local storage in each node. Based on the user configuration,\nthe Local Path Provisioner will create either hostPath or local based\npersistent volume on the node automatically. [ <a class=\"reference external\" href=\"https:\/\/github.com\/rancher\/local-path-provisioner#overview\">source<\/a> ].<\/p>\n<p>For the CI deployment, we did not provide dynamic persistent volume, but\nwe create few local persistent volume, which is storing the service\ncontent on the CRC VM disk.\nTo create the PVs, we choose the solution proposed by <a class=\"reference external\" href=\"https:\/\/github.com\/openstack-k8s-operators\/install_yamls\">OpenStack K8S Operators project<\/a>.<\/p>\n<\/div>\n<div class=\"section\" id=\"connect-to-the-crc-vm\">\n<h3>Connect to the CRC VM<\/h3>\n<p>Sometimes for debuging purpose you would like to connect to the\nCRC instance and check for example the VM logs. The <cite>crc<\/cite> community\nhas prepared a <a class=\"reference external\" href=\"https:\/\/github.com\/crc-org\/crc\/wiki\/Debugging-guide\">documment<\/a> how to do it.\nBelow simple script to connect to the VM.<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"nv\">CRC_IP<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;192.168.130.11&quot;<\/span>\ncat<span class=\"w\"> <\/span><span class=\"s\">&lt;&lt; EOF &gt;&gt; ~\/.ssh\/config<\/span>\n<span class=\"s\">Host crc<\/span>\n<span class=\"s\">    Hostname ${CRC_IP}<\/span>\n<span class=\"s\">    User core<\/span>\n<span class=\"s\">    IdentityFile ~\/.crc\/machines\/crc\/id_rsa<\/span>\n<span class=\"s\">    IdentityFile ~\/.crc\/machines\/crc\/id_ecdsa<\/span>\n<span class=\"s\">    StrictHostKeyChecking no<\/span>\n<span class=\"s\">    UserKnownHostsFile \/dev\/null<\/span>\n<span class=\"s\"> EOF<\/span>\n\n<span class=\"w\"> <\/span>chmod<span class=\"w\"> <\/span><span class=\"m\">0600<\/span><span class=\"w\"> <\/span>~\/.ssh\/config\n<span class=\"w\"> <\/span>ssh<span class=\"w\"> <\/span>-i<span class=\"w\"> <\/span>~\/.crc\/machines\/crc\/id_ecdsa<span class=\"w\"> <\/span>-o<span class=\"w\"> <\/span><span class=\"nv\">StrictHostKeyChecking<\/span><span class=\"o\">=<\/span>no<span class=\"w\"> <\/span>-o<span class=\"w\"> <\/span><span class=\"nv\">UserKnownHostsFile<\/span><span class=\"o\">=<\/span>\/dev\/null<span class=\"w\">  <\/span>core@<span class=\"si\">${<\/span><span class=\"nv\">CRC_IP<\/span><span class=\"si\">}<\/span>\n<\/pre><\/div>\n<\/div>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Checking SF Operator with Kubernetes","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/checking-sf-operator-with-kubernetes.html","rel":"alternate"}},"published":"2022-12-22T00:00:00+00:00","updated":"2022-12-22T00:00:00+00:00","author":{"name":"dpawlik"},"id":"tag:www.softwarefactory-project.io,2022-12-22:\/checking-sf-operator-with-kubernetes.html","summary":"<div class=\"section\" id=\"early-stage-of-sf-operator\">\n<h2>Early stage of SF Operator<\/h2>\n<p>In the beginning, the <cite>sf-operator<\/cite> were deployed on <a class=\"reference external\" href=\"https:\/\/kind.sigs.k8s.io\/\">Kind<\/a> tool,\nbecause it was fast to deploy, easy to enable features like <cite>extraPortMapping<\/cite>,\nconfigure local storage. So it was a perfect tool to run in CI and only for CI.\nAfter a while we realized that \u2026<\/p><\/div>","content":"<div class=\"section\" id=\"early-stage-of-sf-operator\">\n<h2>Early stage of SF Operator<\/h2>\n<p>In the beginning, the <cite>sf-operator<\/cite> were deployed on <a class=\"reference external\" href=\"https:\/\/kind.sigs.k8s.io\/\">Kind<\/a> tool,\nbecause it was fast to deploy, easy to enable features like <cite>extraPortMapping<\/cite>,\nconfigure local storage. So it was a perfect tool to run in CI and only for CI.\nAfter a while we realized that Kind won't be used in production and every\nsimplification in use will need to be applied on production environment like\nKubernetes or OpenShift.<\/p>\n<\/div>\n<div class=\"section\" id=\"the-kubernetes-deployment\">\n<h2>The Kubernetes deployment<\/h2>\n<p>The Kubernetes deployment compared to the Kind tool is more time consuming,\nand it requires more knowledge how to deploy Kubernetes, like: choose\nproper container runtime, configure properly ingress, choose proper\ncontainer networking solution etc.\nBasic Kubernetes deployment is done by using <cite>extra\/kubernetes<\/cite> role from\n<a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/plugins\/gitiles\/software-factory\/sf-infra\/+\/refs\/heads\/master\/roles\/extra\/kubernetes\/\">sf-infra<\/a> project.\nThe role uses <a class=\"reference external\" href=\"https:\/\/cri-o.io\/\">cri-o<\/a> as a container runtime, <a class=\"reference external\" href=\"https:\/\/www.tigera.io\/project-calico\/\">calico<\/a> as networking driver,\n<a class=\"reference external\" href=\"https:\/\/github.com\/kubernetes\/ingress-nginx\/\">ingress<\/a> with localhost port mapping (port 80, 443) and\n<a class=\"reference external\" href=\"https:\/\/github.com\/rancher\/local-path-provisioner\">local-path-provisioner<\/a>.<\/p>\n<p>Here is a simple playbook to deploy Kubernetes:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">Deploy Kubernetes<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">hosts<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">kubernetes.dev<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">roles<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">    <\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">extra\/kubernetes<\/span>\n<\/pre><\/div>\n<div class=\"section\" id=\"why-ingress-port-mapping-is-bound-to-host\">\n<h3>Why ingress port mapping is bound to host?<\/h3>\n<p>We have been using host-bound ingress port mapping with Kind, and we would\nlike to keep doing so far for CI check, because it is simpler and takes less time.\nThat solution might be helpful for development purposes, that it does not\nrequire to attach more resources from your Cloud Provider to the VM or baremetal.\nWith that setup, on one Kubernetes cluster we are able to deploy many\n<cite>sf-operator<\/cite> deployments and communicate with the resources via host ip address,\nbut with a different hostname.\nFor example, in the resource definition of <cite>sf-operator<\/cite>, there is a <cite>fqdn<\/cite> variable:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"c1\"># cat config\/samples\/sf_v1_softwarefactory.yaml<\/span>\n<span class=\"nt\">apiVersion<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">sf.softwarefactory-project.io\/v1<\/span>\n<span class=\"nt\">kind<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">SoftwareFactory<\/span>\n<span class=\"nt\">metadata<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">my-sf<\/span>\n<span class=\"nt\">spec<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">fqdn<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;sftests.com&quot;<\/span>\n<span class=\"nn\">...<\/span>\n<\/pre><\/div>\n<p>And here is how I modified my resource definition to deploy a test instance:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"c1\"># cat config\/samples\/sf_v1_softwarefactory.yaml<\/span>\n<span class=\"nt\">apiVersion<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">sf.softwarefactory-project.io\/v1<\/span>\n<span class=\"nt\">kind<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">SoftwareFactory<\/span>\n<span class=\"nt\">metadata<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">my-sf<\/span>\n<span class=\"nt\">spec<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">fqdn<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;dpawlik.sftests.com&quot;<\/span>\n<span class=\"nn\">...<\/span>\n<\/pre><\/div>\n<p>By changing the <cite>fqdn<\/cite> variable to something different and re-deploy <cite>sf-operator<\/cite>\nin another namespace, you should be able to perform a query:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"nv\">KIND_ID<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;123.123.123.123&quot;<\/span>\ncurl<span class=\"w\"> <\/span><span class=\"s2\">&quot;http:\/\/<\/span><span class=\"si\">${<\/span><span class=\"nv\">KIND_IP<\/span><span class=\"si\">}<\/span><span class=\"s2\">\/&quot;<\/span><span class=\"w\"> <\/span>-H<span class=\"w\"> <\/span><span class=\"s2\">&quot;HOST: etherpad.dpawlik.sftests.com&quot;<\/span>\n\n<span class=\"c1\"># or alternative way<\/span>\n<span class=\"nb\">echo<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;<\/span><span class=\"si\">${<\/span><span class=\"nv\">KIND_IP<\/span><span class=\"si\">}<\/span><span class=\"s2\"> etherpad.dpawlik.sftests.com&quot;<\/span><span class=\"w\"> <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span>sudo<span class=\"w\"> <\/span>tee<span class=\"w\"> <\/span>-a<span class=\"w\"> <\/span>\/ets\/hosts\n<span class=\"nb\">echo<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;<\/span><span class=\"si\">${<\/span><span class=\"nv\">KIND_IP<\/span><span class=\"si\">}<\/span><span class=\"s2\"> etherpad.sftests.com&quot;<\/span><span class=\"w\"> <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span>sudo<span class=\"w\"> <\/span>tee<span class=\"w\"> <\/span>-a<span class=\"w\"> <\/span>\/ets\/hosts\n\n<span class=\"c1\"># make query<\/span>\ncurl<span class=\"w\"> <\/span>-SL<span class=\"w\"> <\/span>etherpad.dpawlik.sftests.com\ncurl<span class=\"w\"> <\/span>-SL<span class=\"w\"> <\/span>etherpad.sftests.com\n<\/pre><\/div>\n<p>With <cite>hostNetwork<\/cite> and added <cite>hostPort<\/cite> for the <cite>ingress-nginx-controller<\/cite>\ndeployment resource, you would be able to reach the resources outside the\nVM\/Baremetal without deploying HAProxy, Cloud Provider resources like\nIP Load Balancer or use alternative ingress configuration. [ <a class=\"reference external\" href=\"https:\/\/kubernetes.github.io\/ingress-nginx\/deploy\/baremetal\/\">samples<\/a> ]<\/p>\n<p>What is worth to mention, the host port binding solution is temporary and\nit is used mostly for development purpose. In the future, our team will consider\nalternative configuration of ingress and local-storage-provisioner to be\nmore compatible with the Kubernetes\/OpenShift deployment, where\nthe user is not an administrator.<\/p>\n<\/div>\n<div class=\"section\" id=\"the-local-path-provisioner\">\n<h3>The local-path-provisioner<\/h3>\n<p>Local Path Provisioner provides a way for the Kubernetes users to utilize\nthe local storage in each node. Based on the user configuration,\nthe Local Path Provisioner will create either hostPath or local based\npersistent volume on the node automatically. [ <a class=\"reference external\" href=\"https:\/\/github.com\/rancher\/local-path-provisioner#overview\">source<\/a> ].<\/p>\n<p>For the CI deployment, we create a local persistent volume, on which the service's\ndata is stored. However we are likely to discard this approach in future\nproduction deployments, because the storage content needs to be available\non all nodes. It is possible to create an NFS storage, or attach the same volume\non all of the nodes, but if you are not an administrator, that solution\nwould be problematic.<\/p>\n<\/div>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Deploying the Quay container registry","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/deploying-the-quay-container-registry.html","rel":"alternate"}},"published":"2022-12-12T00:00:00+00:00","updated":"2023-01-19T00:00:00+00:00","author":{"name":"dpawlik"},"id":"tag:www.softwarefactory-project.io,2022-12-12:\/deploying-the-quay-container-registry.html","summary":"<div class=\"section\" id=\"what-is-quay\">\n<h2>What is Quay ?<\/h2>\n<p>As the infra team we deploy and maintain a Quay service which is a distributed\nand highly available container image registry for the RDO and TripleO project.\nIn the blog post we'll introduce an Ansible role that we have created to\nease Quay deployment and configuration.<\/p>\n<\/div>\n<div class=\"section\" id=\"additional-services\">\n<h2>Additional \u2026<\/h2><\/div>","content":"<div class=\"section\" id=\"what-is-quay\">\n<h2>What is Quay ?<\/h2>\n<p>As the infra team we deploy and maintain a Quay service which is a distributed\nand highly available container image registry for the RDO and TripleO project.\nIn the blog post we'll introduce an Ansible role that we have created to\nease Quay deployment and configuration.<\/p>\n<\/div>\n<div class=\"section\" id=\"additional-services\">\n<h2>Additional services<\/h2>\n<p>The Quay service can communicate with additional services:<\/p>\n<ul class=\"simple\">\n<li><a class=\"reference external\" href=\"https:\/\/www.redhat.com\/en\/topics\/containers\/what-is-clair\">clair<\/a> - is an open source project which provides a tool to monitor the\nsecurity of your containers through the static analysis of vulnerabilities\nin appc and docker containers. [1].<\/li>\n<li><a class=\"reference external\" href=\"https:\/\/access.redhat.com\/documentation\/en-us\/red_hat_quay\/3\/html\/manage_red_hat_quay\/repo-mirroring-in-red-hat-quay\">quay-mirror<\/a> - it is a service\nthat provides mirroring functionality of external repository and pull\nit into current one.<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"how-to-deploy\">\n<h2>How to deploy ?<\/h2>\n<p>The service can be deployed by using dedicated role provided in <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/plugins\/gitiles\/software-factory\/sf-infra\/+\/refs\/heads\/master\/roles\/rdo\/quay\/\">software-factory\/sf-infra project<\/a>\nIt deploys automatically required services such as:<\/p>\n<ul class=\"simple\">\n<li>redis - an in-memory data structure store, used as a distributed,\nin-memory key\u2013value database, cache and message broker, with\noptional durability,<\/li>\n<li>PostgreSQL - open source object-relational database.<\/li>\n<\/ul>\n<p>The playbook below relies on the <cite>quay<\/cite> role to:<\/p>\n<ul class=\"simple\">\n<li>deploy Quay,<\/li>\n<li>setup two superusers that would be an owner of own project.<\/li>\n<\/ul>\n<p>This deployment is minimal and later will see how to use the role to add more\nconfiguration to our Quay deployment.<\/p>\n<p>Two things worth to know:<\/p>\n<ul class=\"simple\">\n<li>Only admin user has password with at least 8 characters.\nOther users password are generated after creating the superuser account.<\/li>\n<li>To generate a token for an organization, after creating a superuser\naccount (after bootstrap), login into the Quay as this user, create\nnew organization: &quot;config&quot;, then inside the organization &quot;config&quot;,\ncreate new application &quot;admin_token&quot;, with:<ul>\n<li>&quot;Administer Organization&quot;,<\/li>\n<li>&quot;Administer Repositories&quot;,<\/li>\n<li>&quot;Create Repositories&quot;,<\/li>\n<li>&quot;View all visible repositories&quot;,<\/li>\n<li>&quot;Read\/Write to any accessible repositories&quot;,<\/li>\n<li>&quot;Administer User&quot; permissions.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<ul class=\"simple\">\n<li>Bootstrap Quay service<\/li>\n<\/ul>\n<div class=\"highlight\"><pre><span><\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">hosts<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">quay.dev<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">vars<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">fqdn<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">quay.dev<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">enable_clair<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">false<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">enable_mirror<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">false<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">self_signed_certs<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">true<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">initial_config<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">false<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">quay_validate_cert<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">false<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">database_secret_key<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">dc52fef2-eed2-4efd-9de6-5af89f86df0a<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">secret_key<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">46bc0133-09b0-486c-bef7-bbe1575f7672<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\"># NOTE: password needs to be at least 8 characters<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">quay_users<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">admin<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">email<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">admin@somemail.com<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">password<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">password<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">token<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;&quot;<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">someuser<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">email<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">someuser@someemail.com<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">token<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;&quot;<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">tasks<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">    <\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">Setup quay<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">include_role<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">rdo\/quay<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">tasks_from<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">main.yml<\/span>\n<\/pre><\/div>\n<p>Next steps for creating new project, users, etc. are done in <a class=\"reference external\" href=\"#Quay-organizations,users,roles...\">section<\/a>.<\/p>\n<img alt=\"loginpage\" src=\"images\/quay-1.jpg\" \/>\n<\/div>\n<div class=\"section\" id=\"quay-organizations-users-roles\">\n<h2>Quay - organizations, users, roles...<\/h2>\n<div class=\"section\" id=\"quay-components\">\n<h3>Quay components<\/h3>\n<ul>\n<li><p class=\"first\">organizations -organizations provide a way of sharing repositories\nunder a common namespace that does not belong to a single user,\nbut rather to many users in a shared setting (such as a company),<\/p>\n<\/li>\n<li><p class=\"first\">teams - organizations are organized into a set of Teams which provide\naccess to a subset of the repositories under that namespace.\nTeams have defined global permissions in the organization: member, creator\nand admin. More info <a class=\"reference external\" href=\"https:\/\/docs.quay.io\/glossary\/teams.html\">here<\/a>,<\/p>\n<\/li>\n<li><p class=\"first\">users - it is a user account that later would connect to the Quay\nby using for example: <cite>podman login<\/cite> command,<\/p>\n<\/li>\n<li><p class=\"first\">robots - it is an account which can be shared by multiple repositories\nthat are owned by a user organization. That account might be helpful,\nwhen you create new container images in CI and you would like just to push\nthe content to the repository,<\/p>\n<\/li>\n<li><p class=\"first\">prototypes - it is default permissions in the organization,<\/p>\n<\/li>\n<li><p class=\"first\">applications - it generates an API <cite>token<\/cite> possible permissions:<\/p>\n<blockquote>\n<ul class=\"simple\">\n<li>administer organization,<\/li>\n<li>administer repositories,<\/li>\n<li>create repositories,<\/li>\n<li>view all visible repositories,<\/li>\n<li>read\/write to any accessible repositories,<\/li>\n<li>super user access,<\/li>\n<li>administer user,<\/li>\n<li>read user information.<\/li>\n<\/ul>\n<\/blockquote>\n<p>The applications can be used by for example <cite>pruner<\/cite> script, to\nset expiration time to the image.<\/p>\n<\/li>\n<li><p class=\"first\">tokens - a string that can communicate with Quay API that has\nalready configured permissions.<\/p>\n<\/li>\n<\/ul>\n<p>Now we enhance our playbook to setup some organizations and playbooks.<\/p>\n<ul class=\"simple\">\n<li>Create project, user, robot etc.:\nAs it was mentioned earlier, token generation are done in application.\nCreate application before execute playbook with <cite>quay-project-creation<\/cite> role.<\/li>\n<\/ul>\n<div class=\"highlight\"><pre><span><\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">hosts<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">quay.dev<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">vars<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">fqdn<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">quay.dev<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">enable_clair<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">false<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">enable_mirror<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">false<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">self_signed_certs<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">true<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">initial_config<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">false<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">quay_validate_cert<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">false<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">database_secret_key<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">dc52fef2-eed2-4efd-9de6-5af89f86df0a<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">secret_key<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">46bc0133-09b0-486c-bef7-bbe1575f7672<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">quay_users<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">      <\/span><span class=\"c1\"># Token for admin is generated during bootstrap.<\/span>\n<span class=\"w\">      <\/span><span class=\"c1\"># Later it is located in: \/var\/data\/quay\/admin_token<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">admin<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">email<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">admin@somemail.com<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">password<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">password<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">token<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;GXI7D7Y4RY7C6KQA23P435SJZTO126WZ&quot;<\/span>\n<span class=\"w\">      <\/span><span class=\"c1\"># Password for someuser is located in: \/var\/data\/quay\/someuser_token<\/span>\n<span class=\"w\">      <\/span><span class=\"c1\"># The token is created in created application.<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">someuser<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">email<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">someuser@someemail.com<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">token<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;33W59Q10MHLWX79G8LAU722DMP2819ZT&quot;<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">quay_organizations<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">      <\/span><span class=\"c1\"># The token variable is necessary just for RDO deployment, where<\/span>\n<span class=\"w\">      <\/span><span class=\"c1\"># new created application token is used by the pruner script to<\/span>\n<span class=\"w\">      <\/span><span class=\"c1\"># cleanup old images. More information in: `Pruner` section.<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">someuser<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">        <\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">myorganization1<\/span>\n<span class=\"w\">          <\/span><span class=\"nt\">token<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;&quot;<\/span>\n<span class=\"w\">        <\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">myorganization2<\/span>\n<span class=\"w\">          <\/span><span class=\"nt\">token<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;&quot;<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">tasks<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">    <\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">Setup quay - reconfigure<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">include_role<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">rdo\/quay<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">tasks_from<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">main.yml<\/span>\n<span class=\"w\">    <\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">Configure Quay projects<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">include_role<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">rdo\/quay-project-creation<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">tasks_from<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">main.yml<\/span>\n<\/pre><\/div>\n<img alt=\"users\" src=\"images\/quay-2.jpg\" \/>\n<div class=\"line-block\">\n<div class=\"line\"><br \/><\/div>\n<\/div>\n<img alt=\"config_application\" src=\"images\/quay-3.jpg\" \/>\n<div class=\"line-block\">\n<div class=\"line\"><br \/><\/div>\n<\/div>\n<img alt=\"application_permissions\" src=\"images\/quay-4.jpg\" \/>\n<div class=\"line-block\">\n<div class=\"line\"><br \/><\/div>\n<\/div>\n<img alt=\"application_permissions_authorize\" src=\"images\/quay-5.jpg\" \/>\n<div class=\"line-block\">\n<div class=\"line\"><br \/><\/div>\n<\/div>\n<img alt=\"repositories\" src=\"images\/quay-6.jpg\" \/>\n<div class=\"line-block\">\n<div class=\"line\"><br \/><\/div>\n<\/div>\n<img alt=\"robotInOrganization\" src=\"images\/quay-7.jpg\" \/>\n<\/div>\n<div class=\"section\" id=\"quay-config-mode\">\n<h3>Quay config mode<\/h3>\n<p>The Quay service has a dedicated startup mode, that the administrator would\nbe able to manage service configuration via Web interface.<\/p>\n<p>By using <cite>quay<\/cite> role from from sf-infra project, there is an Ansible\nvariable: <cite>initial_config<\/cite>.<\/p>\n<p>Below is an example playbook to start the service in &quot;config mode&quot;:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">hosts<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">quay.dev<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">vars<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">fqdn<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">quay.dev<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">enable_clair<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">false<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">enable_mirror<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">false<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">self_signed_certs<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">true<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">initial_config<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">true<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">quay_validate_cert<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">false<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">database_secret_key<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">dc52fef2-eed2-4efd-9de6-5af89f86df0a<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">secret_key<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">46bc0133-09b0-486c-bef7-bbe1575f7672<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">quay_users<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">admin<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">email<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">admin@somemail.com<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">password<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">password<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">token<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;&quot;<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">tasks<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">    <\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">Setup quay<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">include_role<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">rdo\/quay<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">tasks_from<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">main.yml<\/span>\n<\/pre><\/div>\n<p>After playbook finish, the site should be available on <cite>http:\/\/quay.dev<\/cite>\nwith credentials:<\/p>\n<div class=\"highlight\"><pre><span><\/span>username:<span class=\"w\"> <\/span>quayconfig\npassword:<span class=\"w\"> <\/span>secret\n<\/pre><\/div>\n<p>You can always use SSH tuneling:<\/p>\n<div class=\"highlight\"><pre><span><\/span>ssh<span class=\"w\"> <\/span>-L<span class=\"w\"> <\/span><span class=\"m\">8443<\/span>:localhost:443<span class=\"w\"> <\/span>-L<span class=\"w\"> <\/span><span class=\"m\">8080<\/span>:localhost:80<span class=\"w\"> <\/span>centos@quay.dev\n<\/pre><\/div>\n<p>then the site would be available on <cite>http:\/\/localhost:8080<\/cite>.<\/p>\n<img alt=\"quayconfig\" src=\"images\/quay-8.jpg\" \/>\n<\/div>\n<\/div>\n<div class=\"section\" id=\"quay-user-automation\">\n<h2>Quay user automation<\/h2>\n<div class=\"section\" id=\"python-quay-tool\">\n<h3>Python Quay tool<\/h3>\n<p>The Python Quay tool is a Python base script, that helps automate\nthe Quay deployment.\nFor example, there is some new Openstack release and each release\ngot its own dedicated organization just for it.\nThat needs the following manual actions:<\/p>\n<ul class=\"simple\">\n<li>create organization,<\/li>\n<li>create <cite>robot<\/cite> user,<\/li>\n<li>create default permissions for robot user (prototype),<\/li>\n<li>create <cite>creators<\/cite> team that will allow create new repositories,<\/li>\n<li>add the robot user to the team.<\/li>\n<\/ul>\n<p>All of those actions can be done using the Quay Tool which is\ncommunicating with the Quay API and perform required actions.<\/p>\n<p>The tool repository is available <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/plugins\/gitiles\/software-factory\/python-quay-tool\">here<\/a>.<\/p>\n<p>Example commands that you can find in the tool:<\/p>\n<p>Set image to be public:<\/p>\n<div class=\"highlight\"><pre><span><\/span>quaytool<span class=\"w\"> <\/span>--api-url<span class=\"w\"> <\/span>https:\/\/quay.dev\/api\/v1<span class=\"w\"> <\/span>--token<span class=\"w\"> <\/span>&lt;token&gt;<span class=\"w\"> <\/span>--organization<span class=\"w\"> <\/span>myorganization<span class=\"w\"> <\/span>--visibility<span class=\"w\"> <\/span>public\n<\/pre><\/div>\n<p>Specify image repository to be public:<\/p>\n<div class=\"highlight\"><pre><span><\/span>quaytool<span class=\"w\"> <\/span>--api-url<span class=\"w\"> <\/span>https:\/\/quay.dev\/api\/v1<span class=\"w\"> <\/span>--token<span class=\"w\"> <\/span>&lt;token&gt;<span class=\"w\"> <\/span>--organization<span class=\"w\"> <\/span>myorganization<span class=\"w\"> <\/span>--repository<span class=\"w\"> <\/span><span class=\"nb\">test<\/span><span class=\"w\"> <\/span>--repository<span class=\"w\"> <\/span>test2<span class=\"w\"> <\/span>--visibility<span class=\"w\"> <\/span>public\n<\/pre><\/div>\n<p>Set all repository to be private, but skip some of them:<\/p>\n<div class=\"highlight\"><pre><span><\/span>quaytool<span class=\"w\"> <\/span>--api-url<span class=\"w\"> <\/span>https:\/\/quay.dev\/api\/v1<span class=\"w\"> <\/span>--token<span class=\"w\"> <\/span>&lt;token&gt;<span class=\"w\"> <\/span>--organization<span class=\"w\"> <\/span>myorganization<span class=\"w\"> <\/span>--skip<span class=\"w\"> <\/span>test3<span class=\"w\"> <\/span>--skip<span class=\"w\"> <\/span>test4<span class=\"w\"> <\/span>--visibility<span class=\"w\"> <\/span>public\n<\/pre><\/div>\n<p>List all robots in organization:<\/p>\n<div class=\"highlight\"><pre><span><\/span>quay_tool<span class=\"w\"> <\/span>--api-url<span class=\"w\"> <\/span>https:\/\/quay.dev\/api\/v1<span class=\"w\"> <\/span>--organization<span class=\"w\"> <\/span><span class=\"nb\">test<\/span><span class=\"w\"> <\/span>--token<span class=\"w\"> <\/span>sometoken<span class=\"w\"> <\/span>--insecure<span class=\"w\"> <\/span>--list-robots\n<\/pre><\/div>\n<p>Create robot in organization:<\/p>\n<div class=\"highlight\"><pre><span><\/span>quay_tool<span class=\"w\"> <\/span>--api-url<span class=\"w\"> <\/span>https:\/\/quay.dev\/api\/v1<span class=\"w\"> <\/span>--organization<span class=\"w\"> <\/span><span class=\"nb\">test<\/span><span class=\"w\"> <\/span>--token<span class=\"w\"> <\/span>sometoken<span class=\"w\"> <\/span>--create-robot<span class=\"w\"> <\/span>bender\n<\/pre><\/div>\n<p>Set write permissions for a user for repositories inside the\norganziation:<\/p>\n<div class=\"highlight\"><pre><span><\/span>quaytool<span class=\"w\">  <\/span>--api-url<span class=\"w\"> <\/span>https:\/\/quay.dev\/api\/v1<span class=\"w\"> <\/span>--organization<span class=\"w\"> <\/span><span class=\"nb\">test<\/span><span class=\"w\"> <\/span>--token<span class=\"w\"> <\/span>sometoken<span class=\"w\"> <\/span>--user<span class=\"w\"> <\/span>test+cirobot<span class=\"w\"> <\/span>--set-permissions\n<\/pre><\/div>\n<p>Restore deleted tag:<\/p>\n<div class=\"highlight\"><pre><span><\/span>quaytool<span class=\"w\"> <\/span>--api-url<span class=\"w\"> <\/span>https:\/\/quay.dev\/api\/v1<span class=\"w\"> <\/span>--organization<span class=\"w\"> <\/span><span class=\"nb\">test<\/span><span class=\"w\"> <\/span>--token<span class=\"w\"> <\/span>sometoken--tag<span class=\"w\"> <\/span>14ee273e8565960cf6d5b6e26ae92ade<span class=\"w\"> <\/span>--restore-tag\n<\/pre><\/div>\n<p>Set the prototype (default permissions) in the organization. By default\nit creates prototype with write permissions.<\/p>\n<p>For a user:<\/p>\n<div class=\"highlight\"><pre><span><\/span>quaytool<span class=\"w\">  <\/span>--api-url<span class=\"w\"> <\/span>https:\/\/quay.dev\/api\/v1<span class=\"w\"> <\/span>--organization<span class=\"w\"> <\/span><span class=\"nb\">test<\/span><span class=\"w\"> <\/span>--token<span class=\"w\"> <\/span>sometoken<span class=\"w\"> <\/span>--create-prototype<span class=\"w\"> <\/span>--user<span class=\"w\"> <\/span>test+cirobot\n<\/pre><\/div>\n<p>For a team:<\/p>\n<div class=\"highlight\"><pre><span><\/span>quaytool<span class=\"w\">  <\/span>--api-url<span class=\"w\"> <\/span>https:\/\/quay.dev\/api\/v1<span class=\"w\"> <\/span>--organization<span class=\"w\"> <\/span><span class=\"nb\">test<\/span><span class=\"w\"> <\/span>--token<span class=\"w\"> <\/span>sometoken<span class=\"w\"> <\/span>--create-prototype<span class=\"w\"> <\/span>--team<span class=\"w\"> <\/span>creators\n<\/pre><\/div>\n<\/div>\n<div class=\"section\" id=\"pruner\">\n<h3>Pruner<\/h3>\n<p>The RDO team is using <cite>pruner<\/cite> scripts that are communicating with the DLRN (Delorian)\nservice to get the latest promotion hash. Later, images containing the\nhash in the tag, will be skipped from deletion.<\/p>\n<p>The pruner script is using Quay API. To communicate with the API, first you\nneed to create a dedicated application in Quay inside your organization with\nfollowing permissions:<\/p>\n<ul class=\"simple\">\n<li>administer organization,<\/li>\n<li>view all visible repositories.<\/li>\n<\/ul>\n<img alt=\"pruner-application-token\" src=\"images\/quay-9.jpg\" \/>\n<p>You can find the pruner scripts used by the RDO project <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/plugins\/gitiles\/software-factory\/sf-infra\/+\/refs\/heads\/master\/roles\/rdo\/quay\/files\/quay_tag_pruner.py\">here<\/a>.\nOther scripts and crontab job you can find in the <cite>sf-infra<\/cite> project\nin <cite>roles\/rdo\/quay<\/cite>.<\/p>\n<\/div>\n<div class=\"section\" id=\"swagger\">\n<h3>Swagger<\/h3>\n<p>Swagger is a suite of tools for API developers from SmartBear Software and\na former specification upon which the OpenAPI Specification is based.<\/p>\n<p>You can start running the Swagger tool in the container and communicate\nwith Quay API.<\/p>\n<p>How to start Swagger:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"c1\"># Start swagger container<\/span>\npodman<span class=\"w\"> <\/span>run<span class=\"w\"> <\/span>-p<span class=\"w\"> <\/span><span class=\"m\">8888<\/span>:8080<span class=\"w\"> <\/span>-e<span class=\"w\"> <\/span><span class=\"nv\">API_URL<\/span><span class=\"o\">=<\/span>https:\/\/quay.dev\/api\/v1\/discovery<span class=\"w\"> <\/span>docker.io\/swaggerapi\/swagger-ui\n\n<span class=\"c1\"># If you are using local instance with firewall rules, you can tunel<\/span>\n<span class=\"c1\"># the ssh connection and redirect the port<\/span>\n<span class=\"c1\"># OPTIONAL<\/span>\nssh<span class=\"w\"> <\/span>-L<span class=\"w\"> <\/span><span class=\"m\">18888<\/span>:localhost:8888<span class=\"w\"> <\/span>centos@quay.dev\n<\/pre><\/div>\n<p>After running above commands, you should be able to reach the swagger\nWeb UI interface on URL: <cite>http:\/\/quay.dev:8080<\/cite>.<\/p>\n<p>More information how to use Swagger with Quay you can find <a class=\"reference external\" href=\"https:\/\/access.redhat.com\/documentation\/en-us\/red_hat_quay\/3\/html\/red_hat_quay_api_guide\/using_the_red_hat_quay_api#accessing_your_quay_api_from_a_web_browser\">here<\/a>.<\/p>\n<\/div>\n<div class=\"section\" id=\"example-how-to-automate-quay-organization-deployment-base-on-tripleo-release\">\n<h3>Example how to automate Quay organization deployment base on TripleO release<\/h3>\n<p>The RDO Project has automated the creation of projects, users, robots, prototypes, etc.\nThere is a dedicated <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/plugins\/gitiles\/software-factory\/sf-infra\/+\/refs\/heads\/master\/roles\/rdo\/quay-project-creation\/\">role<\/a>.<\/p>\n<p>The bootstrap new organization in <cite>tripleo<\/cite> project is done in two steps:<\/p>\n<ul class=\"simple\">\n<li>Add into the <cite>quay_organizations<\/cite> Ansible variable, to the <cite>tripleo<\/cite> object a\nnew entry, that creates a new organization - let's call it <cite>my-new-project<\/cite>.\nThat entry should have empty value for <cite>token<\/cite> parameter, for example:<\/li>\n<\/ul>\n<div class=\"highlight\"><pre><span><\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">hosts<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">quay.dev<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">vars<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">quay_organizations<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">tripleo<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">        <\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">tripleomastercentos9<\/span>\n<span class=\"w\">          <\/span><span class=\"nt\">token<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;some<\/span><span class=\"nv\"> <\/span><span class=\"s\">token<\/span><span class=\"nv\"> <\/span><span class=\"s\">generated<\/span><span class=\"nv\"> <\/span><span class=\"s\">in<\/span><span class=\"nv\"> <\/span><span class=\"s\">tripleomastercentos9<\/span><span class=\"nv\"> <\/span><span class=\"s\">organization<\/span><span class=\"nv\"> <\/span><span class=\"s\">application&quot;<\/span>\n<span class=\"w\">          <\/span><span class=\"nt\">prune_days<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">7<\/span>\n<span class=\"w\">        <\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">my-new-project<\/span>\n<span class=\"w\">          <\/span><span class=\"nt\">token<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;&quot;<\/span>\n<\/pre><\/div>\n<ul class=\"simple\">\n<li>When the Ansible run is done, create a new application token inside the\nnew created organization ( <cite>my-new-project<\/cite> ), and modify the playbook\nvariables and add into your organization a token, that you generated.\nThe step how to generate the token has been described in the <a class=\"reference internal\" href=\"#pruner\">Pruner<\/a> section.\nNow the playbook vars will look like:<\/li>\n<\/ul>\n<div class=\"highlight\"><pre><span><\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">hosts<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">quay.dev<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">vars<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">quay_organizations<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">tripleo<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">        <\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">tripleomastercentos9<\/span>\n<span class=\"w\">          <\/span><span class=\"nt\">token<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;some<\/span><span class=\"nv\"> <\/span><span class=\"s\">token<\/span><span class=\"nv\"> <\/span><span class=\"s\">generated<\/span><span class=\"nv\"> <\/span><span class=\"s\">in<\/span><span class=\"nv\"> <\/span><span class=\"s\">tripleomastercentos9<\/span><span class=\"nv\"> <\/span><span class=\"s\">organization<\/span><span class=\"nv\"> <\/span><span class=\"s\">application&quot;<\/span>\n<span class=\"w\">          <\/span><span class=\"nt\">prune_days<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">7<\/span>\n<span class=\"w\">        <\/span><span class=\"p p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">my-new-project<\/span>\n<span class=\"w\">          <\/span><span class=\"nt\">token<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;zjakss7oXpNAM8F22iB02abb9ysWb3rbN2raAApm&quot;<\/span>\n<span class=\"w\">          <\/span><span class=\"nt\">prune_days<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l l-Scalar l-Scalar-Plain\">7<\/span>\n<\/pre><\/div>\n<p>Example of the whole Ansible playbook, you can find in <a class=\"reference internal\" href=\"#quay-components\">Quay components<\/a> section.<\/p>\n<p>Also please note, that same actions can be perfomed without the Ansible\nby using Web browser and Quay Web site.\nAll steps are described in the <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/plugins\/gitiles\/software-factory\/python-quay-tool\/+\/refs\/heads\/master\/README.md#basic-workflow-how-to-setup-new-organziation\">README file<\/a>.<\/p>\n<\/div>\n<div class=\"section\" id=\"documentation\">\n<h3>Documentation<\/h3>\n<p>Quay provides documentation that has a troubleshooting chapter.\nThe documentation can be found <a class=\"reference external\" href=\"https:\/\/docs.quay.io\/\">here<\/a>.<\/p>\n<\/div>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2022 Nov 18 to Dec 07 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2022-nov-18-to-dec-07-summary.html","rel":"alternate"}},"published":"2022-12-07T10:00:00+00:00","updated":"2022-12-07T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2022-12-07:\/sprint-2022-nov-18-to-dec-07-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<div class=\"section\" id=\"next-sf-release-sf-3-8\">\n<h3>next sf release (sf-3.8)<\/h3>\n<ul class=\"simple\">\n<li>We are working to bump grafana to 9.2.6 for sf-3.8, we builded grafyaml-ubi-8 container and are doing the integration right now<\/li>\n<li>we implemented a way to create a {tenant \u2026<\/li><\/ul><\/div><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<div class=\"section\" id=\"next-sf-release-sf-3-8\">\n<h3>next sf release (sf-3.8)<\/h3>\n<ul class=\"simple\">\n<li>We are working to bump grafana to 9.2.6 for sf-3.8, we builded grafyaml-ubi-8 container and are doing the integration right now<\/li>\n<li>we implemented a way to create a {tenant}_zuul_admin role automatically in keycloak whenever a tenant is added in the resources. Zuul is then configured so that whoever holds that role is an admin on the tenant (web UI)<\/li>\n<li>we worked on updating the documentation on SSO and user management<\/li>\n<li>We have removed sfmanager usage removal from sf-ci <a class=\"reference external\" href=\"https:\/\/issues.redhat.com\/browse\/RHOSZUUL-1114\">https:\/\/issues.redhat.com\/browse\/RHOSZUUL-1114<\/a><\/li>\n<li>We have removed cauth related<\/li>\n<li>We worked on a sf-telegraf role base on container for mitigate the telegraf repository issue: <a class=\"reference external\" href=\"https:\/\/github.com\/influxdata\/telegraf\/issues\/12303\">https:\/\/github.com\/influxdata\/telegraf\/issues\/12303<\/a><\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"sf-operator-sf-4-0\">\n<h3>SF-operator (sf-4.0)<\/h3>\n<ul class=\"simple\">\n<li>We set the Zuul scheduler main.yaml automatically generated from the resources defintion <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/26703\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-operator\/+\/26703<\/a><\/li>\n<li>We enabled a first flow for the config-update post job<\/li>\n<\/ul>\n<\/div>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2022 Oct 28 to Nov 16 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2022-oct-28-to-nov-16-summary.html","rel":"alternate"}},"published":"2022-11-16T10:00:00+00:00","updated":"2022-11-16T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2022-11-16:\/sprint-2022-oct-28-to-nov-16-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We proposed a opensearch dashboards backup object tools<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<div class=\"section\" id=\"next-sf-release-sf-3-8\">\n<h3>next sf release (sf-3.8)<\/h3>\n<ul class=\"simple\">\n<li>We bumped opensearch and opensearch dashboards to 2.4.0 on sf-master<\/li>\n<li>We bumped zuul to 8.0.1 and nodepool to 8 \u2026<\/li><\/ul><\/div><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We proposed a opensearch dashboards backup object tools<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<div class=\"section\" id=\"next-sf-release-sf-3-8\">\n<h3>next sf release (sf-3.8)<\/h3>\n<ul class=\"simple\">\n<li>We bumped opensearch and opensearch dashboards to 2.4.0 on sf-master<\/li>\n<li>We bumped zuul to 8.0.1 and nodepool to 8.0.0 on sf-master<\/li>\n<li>We took time to investigate CI flakyness<\/li>\n<li>Most of the keycloak patch chain has been merged<\/li>\n<li>We added support for default admin rules related to zuul tenants<\/li>\n<li>We worked on doc update for the keycloak migration<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"sf-operator-sf-4-0\">\n<h3>SF-operator (sf-4.0)<\/h3>\n<ul class=\"simple\">\n<li>We added Landing Page to SF Operator<\/li>\n<li>We improved SF Operator Output<\/li>\n<li>We fixed an issue with ingress traffic in Kubernetes<\/li>\n<li>We proposed a change that is providing MetalLB traffic that can be used in the future deployment where ingress will be binding to the loadbalancer<\/li>\n<li>We created simply test to verify service ingress<\/li>\n<li>We added securityContenxt to some services and now is more stable<\/li>\n<li>We wrote some ADRs (Architecture Design Records) <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/q\/topic:sf-operator-adr\">https:\/\/softwarefactory-project.io\/r\/q\/topic:sf-operator-adr<\/a><\/li>\n<\/ul>\n<\/div>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2022 Oct 07 to Oct 26 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2022-oct-07-to-oct-26-summary.html","rel":"alternate"}},"published":"2022-10-26T10:00:00+00:00","updated":"2022-10-26T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2022-10-26:\/sprint-2022-oct-07-to-oct-26-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We added a feature for logscraper that it should not quit the service when zuul api is not reachable<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<div class=\"section\" id=\"next-sf-release-sf-3-8\">\n<h3>next sf release (sf-3.8)<\/h3>\n<ul class=\"simple\">\n<li>We bumped zuul version to 7.1.0<\/li>\n<li>We updated the sf-ui \u2026<\/li><\/ul><\/div><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We added a feature for logscraper that it should not quit the service when zuul api is not reachable<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<div class=\"section\" id=\"next-sf-release-sf-3-8\">\n<h3>next sf release (sf-3.8)<\/h3>\n<ul class=\"simple\">\n<li>We bumped zuul version to 7.1.0<\/li>\n<li>We updated the sf-ui to adapt to Keycloak<\/li>\n<li>We worked on tracking issues with SF-CI to remove flakyness<\/li>\n<li>We worked on switching from cauth to keycloak, various fixes along the way to opensearch, grafana, managesf<\/li>\n<li>We updated the Landing Page icons for Keycloak and Opensearch Dashboards<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"sf-operator-sf-4-0\">\n<h3>SF-operator (sf-4.0)<\/h3>\n<ul class=\"simple\">\n<li>We added Managesf, Hound, GerritBot, Cgit, Postfix and Grafana Operators<\/li>\n<li>We started improving roles by adding securityContext or move some containers that were using &quot;root&quot; as a user that later would be easier to move the CI to Openshift<\/li>\n<\/ul>\n<\/div>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2022 Sep 16 to Oct 05 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2022-sep-16-to-oct-05-summary.html","rel":"alternate"}},"published":"2022-10-05T10:00:00+00:00","updated":"2022-10-05T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2022-10-05:\/sprint-2022-sep-16-to-oct-05-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We reviewed the tracing spec implementation.<\/li>\n<li>We import Lukas Piwowarski Opensearch Dashboards objects that contains visualizations and dashboard<\/li>\n<li>We merged few changes related to subunit in ci-log-processing<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<div class=\"section\" id=\"next-sf-release-sf-3-8\">\n<h3>next sf release (sf-3.8)<\/h3>\n<ul class=\"simple\">\n<li>We added feature on \u2026<\/li><\/ul><\/div><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We reviewed the tracing spec implementation.<\/li>\n<li>We import Lukas Piwowarski Opensearch Dashboards objects that contains visualizations and dashboard<\/li>\n<li>We merged few changes related to subunit in ci-log-processing<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<div class=\"section\" id=\"next-sf-release-sf-3-8\">\n<h3>next sf release (sf-3.8)<\/h3>\n<ul class=\"simple\">\n<li>We added feature on sf-config to use public ip for services like zk for external instances<\/li>\n<li>We added feature to not ssh-keyscan hosts in private clouds but which are part of the deployment<\/li>\n<li>We made good progress on transitioning from cauth to keycloak - we're still seeing a few problems with tenant deployments but we are ironing out the bugs.<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"sf-operator-sf-4-0\">\n<h3>SF-operator (sf-4.0)<\/h3>\n<ul class=\"simple\">\n<li>We've added the setup for Zuul to authenticate via Keycloak + default admin rules<\/li>\n<li>We've added the setup for Opensearch Dashboards to authenticate via Keycloak<\/li>\n<li>We've worked on having user authenticated via Keycloak to be able to get write access to opensearch indices<\/li>\n<li>We enabled CI job that is validating sf operator with Kubernetes<\/li>\n<\/ul>\n<\/div>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Introducing an effects system for Monocle","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/introducing-an-effects-system-for-monocle.html","rel":"alternate"}},"published":"2022-09-27T00:00:00+00:00","updated":"2022-09-27T00:00:00+00:00","author":{"name":"tristanC"},"id":"tag:www.softwarefactory-project.io,2022-09-27:\/introducing-an-effects-system-for-monocle.html","summary":"<style type=\"text\/css\">\n\n  .literal {\n    border-radius: 6px;\n    padding: 1px 1px;\n    background-color: rgba(27,31,35,.05);\n  }\n\n<\/style><!-- This work is licensed under the Creative Commons Attribution 4.0 International License.\n     To view a copy of this license, visit http:\/\/creativecommons.org\/licenses\/by\/4.0\/\n     or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.\n--><p>This blog post explains the reasons we integrated an <a class=\"reference external\" href=\"https:\/\/en.wikipedia.org\/wiki\/Effect_system\">effect system<\/a> in\n<a class=\"reference external\" href=\"https:\/\/changemetrics.io\/\">Monocle<\/a>. This post aims to be beginner friendly. We understand that\nsome concepts sound intimidating and we hope that this post demystifies\nthem a bit.<\/p>\n<p>First \u2026<\/p>","content":"<style type=\"text\/css\">\n\n  .literal {\n    border-radius: 6px;\n    padding: 1px 1px;\n    background-color: rgba(27,31,35,.05);\n  }\n\n<\/style><!-- This work is licensed under the Creative Commons Attribution 4.0 International License.\n     To view a copy of this license, visit http:\/\/creativecommons.org\/licenses\/by\/4.0\/\n     or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.\n--><p>This blog post explains the reasons we integrated an <a class=\"reference external\" href=\"https:\/\/en.wikipedia.org\/wiki\/Effect_system\">effect system<\/a> in\n<a class=\"reference external\" href=\"https:\/\/changemetrics.io\/\">Monocle<\/a>. This post aims to be beginner friendly. We understand that\nsome concepts sound intimidating and we hope that this post demystifies\nthem a bit.<\/p>\n<p>First, it describes the context and its main issue. Next, it defines key\nterms and concepts. Finally, it shows how we used the <a class=\"reference external\" href=\"https:\/\/github.com\/haskell-effectful\/effectful#readme\">effectful<\/a>\nlibrary to improve Monocle composability.<\/p>\n<div class=\"section\" id=\"context-and-problem-statement\">\n<h2>Context and problem statement<\/h2>\n<p>Monocle components are implemented using dedicated actions. The goal is\nto limit the available side-effects and to maintain a clear separation\nof concerns.<\/p>\n<p>The current implementation is based on the 'ReaderT over IO' pattern and\nit is affected by a problem known as the 'n\u00b2 instances'. The issue is\nthat adding new side-effects requires unnecessary modifications, which\nlimit the composability and testability of the components.<\/p>\n<\/div>\n<div class=\"section\" id=\"what-is-a-side-effect\">\n<h2>What is a side-effect?<\/h2>\n<p>A function has <a class=\"reference external\" href=\"https:\/\/en.wikipedia.org\/wiki\/Side_effect_(computer_science)\">side-effects<\/a> when it modifies a state outside of its\nlocal environment. Common examples of side-effects include:<\/p>\n<ul class=\"simple\">\n<li>Accessing the filesystem,<\/li>\n<li>Executing another program, or<\/li>\n<li>Connecting to a network service.<\/li>\n<\/ul>\n<p>In other words, a function has side-effects if its output does not\ndepend solely on its input. It's valuable to identify side-effects\nbecause they require extra care when testing and optimizing.<\/p>\n<p>The Haskell type system defines function with side-effects by wrapping\nthe return value in the IO action. For example, the standard 'readFile'\nfunction is defined as <tt class=\"docutils literal\">FilePath <span class=\"pre\">-&gt;<\/span> IO String<\/tt>: given a file path,\n'readFile' returns an IO action that produces a string.<\/p>\n<p>Previously, in Monocle, we used the 'ReaderT over IO' pattern.<\/p>\n<\/div>\n<div class=\"section\" id=\"what-is-a-reader\">\n<h2>What is a reader?<\/h2>\n<p>A reader provides an environment to the functions. For example, instead\nof passing the environment using explicit parameters:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"nf\">computeMetric<\/span><span class=\"w\"> <\/span><span class=\"ow\">::<\/span><span class=\"w\"> <\/span><span class=\"kt\">Logger<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">Database<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">Param<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">IO<\/span><span class=\"w\"> <\/span><span class=\"kt\">Metric<\/span>\n<\/pre><\/div>\n<p>A reader can declare the available environment by wrapping the return\nvalue:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"nf\">computeMetric<\/span><span class=\"w\"> <\/span><span class=\"ow\">::<\/span><span class=\"w\"> <\/span><span class=\"kt\">Param<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">ReaderT<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"kt\">Logger<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"kt\">Database<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"kt\">IO<\/span><span class=\"w\"> <\/span><span class=\"kt\">Metric<\/span>\n<\/pre><\/div>\n<p>This function signature means: given a parameter, 'computeMetric'\nreturns a reader action that procudes a metric using the\n<tt class=\"docutils literal\">(Logger, Database)<\/tt> environment. This lets us focus on the business\nlogic without manually handling the environment. This is particularly\nconvenient for intermediary functions which don't have to pass the\nenvironment parameters around. In other words, the reader re-arranges\nthe function's parameters to move the environment out of the way.<\/p>\n<p>A reader is analogous to this Python construct:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"k\">class<\/span> <span class=\"nc\">Api<\/span><span class=\"p\">:<\/span>\n    <span class=\"k\">def<\/span> <span class=\"fm\">__init__<\/span><span class=\"p\">(<\/span><span class=\"bp\">self<\/span><span class=\"p\">,<\/span> <span class=\"n\">logger<\/span><span class=\"p\">,<\/span> <span class=\"n\">database<\/span><span class=\"p\">):<\/span>\n        <span class=\"bp\">self<\/span><span class=\"o\">.<\/span><span class=\"n\">logger<\/span> <span class=\"o\">=<\/span> <span class=\"n\">logger<\/span>\n        <span class=\"bp\">self<\/span><span class=\"o\">.<\/span><span class=\"n\">database<\/span> <span class=\"o\">=<\/span> <span class=\"n\">database<\/span>\n\n    <span class=\"k\">def<\/span> <span class=\"nf\">compute_metric<\/span><span class=\"p\">(<\/span><span class=\"bp\">self<\/span><span class=\"p\">,<\/span> <span class=\"n\">param<\/span><span class=\"p\">)<\/span> <span class=\"o\">-&gt;<\/span> <span class=\"n\">Metric<\/span><span class=\"p\">:<\/span>\n        <span class=\"o\">...<\/span>\n<\/pre><\/div>\n<p>This 'Api' object attaches the environment to a general purpose <tt class=\"docutils literal\">self<\/tt>\nreference which is passed on to every object method. The\n<tt class=\"docutils literal\">compute_metric<\/tt> method can freely read and modify the <tt class=\"docutils literal\">self<\/tt>\nattributes. On the other hand, the reader action precisely describes the\navailable environment for the <tt class=\"docutils literal\">computeMetric<\/tt> function.<\/p>\n<p>The next sections present how Monocle used to be implemented and what is\nthe benefit of using an effect system.<\/p>\n<\/div>\n<div class=\"section\" id=\"monocle-action-contexts\">\n<h2>Monocle action contexts<\/h2>\n<p>The Monocle component actions were defined as:<\/p>\n<ul class=\"simple\">\n<li><tt class=\"docutils literal\">newtype AppAction a = AppAction (ReaderT AppEnv IO a)<\/tt> to\ninitialize the index and serve the API.<\/li>\n<li><tt class=\"docutils literal\">newtype QueryActon a = QueryAction (ReaderT QueryEnv IO a)<\/tt> to\nserve user metric.<\/li>\n<li><tt class=\"docutils literal\">newtype CrawlerAction a = CrawlerAction (ReaderT CrawlerEnv IO a)<\/tt>\nto collect changes data.<\/li>\n<\/ul>\n<p>Instead of using the new types, the individual functions used mtl-style\ntypeclass constraints to enable generic implementations. For example\nMonocle had:<\/p>\n<ul class=\"simple\">\n<li><tt class=\"docutils literal\">class TimeContext m<\/tt>, to enable reading the local time,<\/li>\n<li><tt class=\"docutils literal\">class RetryContext m<\/tt>, to catch network error and retry the action\nwith exponential backoff,<\/li>\n<li><tt class=\"docutils literal\">class LoggerContext m<\/tt>, to log messages, and<\/li>\n<li><tt class=\"docutils literal\">class DatabaseContext m<\/tt>, to access the database.<\/li>\n<\/ul>\n<p>Such typeclasses are different from Python's class: a typeclass defines\na set of methods that is shared across multiple types. This is analogous\nto the Rust <a class=\"reference external\" href=\"https:\/\/doc.rust-lang.org\/book\/ch10-02-traits.html\">trait system<\/a>. That means each action needed to provide\nits own instance, for example:<\/p>\n<ul class=\"simple\">\n<li><tt class=\"docutils literal\">instance DatabaseContext AppAction<\/tt><\/li>\n<li><tt class=\"docutils literal\">instance DatabaseContext QueryAction<\/tt><\/li>\n<\/ul>\n<p>Monocle also defined super constraints for the component code to avoid\nlisting the individual constraint:<\/p>\n<ul class=\"simple\">\n<li><tt class=\"docutils literal\">class (TimeContext m, LoggerContext m, DatabaseContext m) =&gt; AppContext m<\/tt><\/li>\n<li><tt class=\"docutils literal\">class (LoggerContext m, DatabaseContext m) =&gt; QueryContext m<\/tt><\/li>\n<li><tt class=\"docutils literal\">class (TimeContext m, RetryContext m) =&gt; CrawlerContext m<\/tt><\/li>\n<\/ul>\n<p>So that the <tt class=\"docutils literal\">computeMetric<\/tt> function was defined as:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"nf\">computeMetric<\/span><span class=\"w\"> <\/span><span class=\"ow\">::<\/span><span class=\"w\"> <\/span><span class=\"kt\">QueryContext<\/span><span class=\"w\"> <\/span><span class=\"n\">m<\/span><span class=\"w\"> <\/span><span class=\"ow\">=&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">Param<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"n\">m<\/span><span class=\"w\"> <\/span><span class=\"kt\">Metric<\/span>\n<\/pre><\/div>\n<p>Similarly, the <tt class=\"docutils literal\">getChanges<\/tt> crawler function was defined as:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"nf\">getChanges<\/span><span class=\"w\"> <\/span><span class=\"ow\">::<\/span><span class=\"w\"> <\/span><span class=\"kt\">CrawlerContext<\/span><span class=\"w\"> <\/span><span class=\"n\">m<\/span><span class=\"w\"> <\/span><span class=\"ow\">=&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">Repository<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"n\">m<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"kt\">Changes<\/span><span class=\"p\">]<\/span>\n<\/pre><\/div>\n<p>Pros:<\/p>\n<ul class=\"simple\">\n<li>Restricted side effects: the function can't do arbitrary IO.<\/li>\n<li>The constraints can be implemented differently depending on the\ncontext.<\/li>\n<li>The types enforce the available effects. For example, accessing the\ndatabase from a crawler context is a compile time error.<\/li>\n<\/ul>\n<p>Cons:<\/p>\n<ul class=\"simple\">\n<li>Adding a new contraint requires adding new instances, the so called\n'n\u00b2 instances' problem.<\/li>\n<li>This abstraction has an overhead cost, though it was not noticable in\nMonocle performance.<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"effects-system\">\n<h2>Effects system<\/h2>\n<p>To improve the Monocle code base, we replaced the mtl-style constraints\nwith an effect system. Instead of using constraints for the execution\ncontext, denoted <tt class=\"docutils literal\">m<\/tt>, Monocle now uses a list of effect constraints,\ndenoted <tt class=\"docutils literal\">es<\/tt>, along with the <tt class=\"docutils literal\">Eff<\/tt> action provided by the\n<a class=\"reference external\" href=\"https:\/\/github.com\/haskell-effectful\/effectful#readme\">effectful<\/a> library.<\/p>\n<p>The main difference is that the effect's environments are defined\nindividually, and we no longer have to implement the <tt class=\"docutils literal\">m<\/tt> constraint\nfor every context. Effectful effectively lets us easily compose a list\nof readers. To learn more about this technique, checkout the\n<a class=\"reference external\" href=\"https:\/\/hackage.haskell.org\/package\/effectful-core-2.1.0.0\/docs\/Effectful-Dispatch-Static.html\">Effectful.Dispatch.Static<\/a> module documentation.<\/p>\n<p>We replaced the super contexts with a type alias to list all the\nnecessary effects in one place:<\/p>\n<ul class=\"simple\">\n<li><tt class=\"docutils literal\">type QueryEffects es = [LoggerEffect,DatabaseEffect] :&gt;&gt; es<\/tt><\/li>\n<li><tt class=\"docutils literal\">type CrawlerEffects es = [TimeEffect,RetryEffect] :&gt;&gt; es<\/tt><\/li>\n<\/ul>\n<p>And the <tt class=\"docutils literal\">computeMetric<\/tt> and <tt class=\"docutils literal\">getChanges<\/tt> functions are now defined\nas:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"nf\">computeMetric<\/span><span class=\"w\"> <\/span><span class=\"ow\">::<\/span><span class=\"w\"> <\/span><span class=\"kt\">QueryEffects<\/span><span class=\"w\"> <\/span><span class=\"n\">es<\/span><span class=\"w\"> <\/span><span class=\"ow\">=&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">Param<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">Eff<\/span><span class=\"w\"> <\/span><span class=\"n\">es<\/span><span class=\"w\"> <\/span><span class=\"kt\">Metric<\/span>\n<span class=\"nf\">getChanges<\/span><span class=\"w\"> <\/span><span class=\"ow\">::<\/span><span class=\"w\"> <\/span><span class=\"kt\">CrawlerEffects<\/span><span class=\"w\"> <\/span><span class=\"n\">es<\/span><span class=\"w\"> <\/span><span class=\"ow\">=&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">Repository<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">Eff<\/span><span class=\"w\"> <\/span><span class=\"n\">es<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"kt\">Changes<\/span><span class=\"p\">]<\/span>\n<\/pre><\/div>\n<p>The initial refactor aimed for a drop-in replacement so that only the\nfunction's signature changed from <tt class=\"docutils literal\">m<\/tt> to <tt class=\"docutils literal\">Eff es<\/tt>. If you are\ncurious, you can check the <a class=\"reference external\" href=\"https:\/\/github.com\/change-metrics\/monocle\/pull\/954\">PR#954<\/a> which introduced the new\nimplementation.<\/p>\n<p>Pros:<\/p>\n<ul class=\"simple\">\n<li>This new implementation is arguably simpler: an effect is defined\nonly once.<\/li>\n<li>Effectful enables seamless integration with the existing Haskell\necosystem.<\/li>\n<li>Eff is fast: the effect lookup is <tt class=\"docutils literal\">O(1)<\/tt> according to its\n<a class=\"reference external\" href=\"https:\/\/hackage.haskell.org\/package\/effectful-core-2.1.0.0\/docs\/Effectful-Internal-Effect.html#t:Effect\">documentation<\/a>.<\/li>\n<\/ul>\n<p>Cons:<\/p>\n<ul class=\"simple\">\n<li>The effectful library is relatively new and the ecosystem is still\nimmature.<\/li>\n<li>The Eff implementation is more complicated than a simple Reader, for\nexample the process known as <a class=\"reference external\" href=\"https:\/\/github.com\/fpco\/unliftio#unlifting-in-2-minutes\">unlifting<\/a> requires extra attentions\nwhen running concurrently.<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"conclusion\">\n<h2>Conclusion<\/h2>\n<p>We are satisfied with the transition and we are looking forward to\ncontributing to the effectful ecosystem by sharing the Monocle\nimplementations.<\/p>\n<p>Please note that behind the 'Action' and 'Context' mentioned in this\npost, there is a fundamental structure called a <a class=\"reference external\" href=\"https:\/\/en.wikipedia.org\/wiki\/Monad_(functional_programming)\">Monad<\/a>. If you are not\nfamiliar with the concept already, we recommend this <a class=\"reference external\" href=\"https:\/\/www.youtube.com\/watch?v=t1e8gqXLbsU\">computerphile\nvideo<\/a> by Graham Hutton.<\/p>\n<p>Thanks for reading!<\/p>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Introduction to <\/> htmx through a Simple WEB chat application","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/introduction-to-htmx-through-a-simple-web-chat-application.html","rel":"alternate"}},"published":"2022-09-26T00:00:00+00:00","updated":"2022-09-26T00:00:00+00:00","author":{"name":"Fabien"},"id":"tag:www.softwarefactory-project.io,2022-09-26:\/introduction-to-htmx-through-a-simple-web-chat-application.html","summary":"<style type=\"text\/css\">\n\n  .literal {\n    border-radius: 6px;\n    padding: 1px 1px;\n    background-color: rgba(27,31,35,.05);\n  }\n\n<\/style><p>This post aims to introduce <a class=\"reference external\" href=\"https:\/\/htmx.org\">htmx<\/a> usage through a very simple chat\napplication. We'll expose and explain some code snippets from a\n<a class=\"reference external\" href=\"https:\/\/github.com\/morucci\/schat\/tree\/16291940ab602a7c888c1a0f82acd995b24ae267\">playground project<\/a> named simple chat.<\/p>\n<img alt=\"sChat UI\" src=\"images\/schat.png\" \/>\n<p>The playground application is written in Haskell but htmx usage is \u2026<\/p>","content":"<style type=\"text\/css\">\n\n  .literal {\n    border-radius: 6px;\n    padding: 1px 1px;\n    background-color: rgba(27,31,35,.05);\n  }\n\n<\/style><p>This post aims to introduce <a class=\"reference external\" href=\"https:\/\/htmx.org\">htmx<\/a> usage through a very simple chat\napplication. We'll expose and explain some code snippets from a\n<a class=\"reference external\" href=\"https:\/\/github.com\/morucci\/schat\/tree\/16291940ab602a7c888c1a0f82acd995b24ae267\">playground project<\/a> named simple chat.<\/p>\n<img alt=\"sChat UI\" src=\"images\/schat.png\" \/>\n<p>The playground application is written in Haskell but htmx usage is not\ntied to a specific language, and it can be used with any server side\nlanguage such as Python.<\/p>\n<div class=\"section\" id=\"what-is-htmx-1\">\n<span id=\"what-is-htmx\"><\/span><h2>What is htmx ?<\/h2>\n<p>htmx provides an easy way to build dynamic WEB frontends without the\nneed to write a ton of Javascript code. htmx is itself a Javascript\nlibrary that:<\/p>\n<ul class=\"simple\">\n<li>reacts to HTML attributes to fire events like AJAX requests<\/li>\n<li>updates the DOM based on server responses<\/li>\n<\/ul>\n<p>With htmx any HTML element can issue requests to the backend, and every\nelement can be updated via the backend (not just the entire page).<\/p>\n<\/div>\n<div class=\"section\" id=\"why-1\">\n<span id=\"why\"><\/span><h2>Why ?<\/h2>\n<p>Htmx brings lot of facilities to build WEB applications using only your\nfavorite backend language and simply relying on htmx facilities for\nhandling frontend events and rendering frontend elements. Indeed it is\noften difficult to manage two different toolchains for the backend and\nthe frontend that needs to share data types.<\/p>\n<\/div>\n<div class=\"section\" id=\"usage-of-htmx-to-build-the-schat-application\">\n<h2>Usage of htmx to build the sChat application<\/h2>\n<p>To build the application we relied on serveral libraries:<\/p>\n<ul class=\"simple\">\n<li><a class=\"reference external\" href=\"https:\/\/docs.servant.dev\">Servant<\/a>: To build the web application (to handle routes and define\nroute' handlers)<\/li>\n<li><a class=\"reference external\" href=\"https:\/\/hackage.haskell.org\/package\/websockets\">WebSockets<\/a>: To setup a WebSocket handler for enabling\ncommunication between the frontend and the backend<\/li>\n<li><a class=\"reference external\" href=\"https:\/\/chrisdone.com\/posts\/lucid\/\">Lucid<\/a>: A DSL to render HTML<\/li>\n<li><a class=\"reference external\" href=\"https:\/\/htmx.org\">htmx<\/a>: Run on the client's browser, to render the application<\/li>\n<li><a class=\"reference external\" href=\"https:\/\/tailwindcss.com\/\">Tailwindcss<\/a>: Run on the client's browser, to apply CSS styles<\/li>\n<\/ul>\n<p>But to keep this post short, we'll not dig into the usage of Servant or\nLucid but just focus on interactions between the frontend and the\nbackend using htmx.<\/p>\n<\/div>\n<div class=\"section\" id=\"htmx-usage-to-build-schat\">\n<h2>htmx usage to build sChat<\/h2>\n<div class=\"section\" id=\"backend-http-endpoints\">\n<h3>Backend HTTP endpoints<\/h3>\n<p>sChat implements three endpoints under the following routes:<\/p>\n<ul class=\"simple\">\n<li>&quot;\/&quot;: This handler returns plain HTML data. It serves a HTML bootstrap\nto connect to the WebSocket.<\/li>\n<li>&quot;xstatic&quot;: The handler serves static files. It serves htmx and\ntailwindcss JS libraries<\/li>\n<li>&quot;ws&quot;: The WebSocket handler<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"the-root-endpoint-1\">\n<span id=\"the-root-endpoint\"><\/span><h3>The root &quot;\/&quot; endpoint<\/h3>\n<div class=\"highlight\"><pre><span><\/span><span class=\"nf\">sChatHTMLHandler<\/span><span class=\"w\"> <\/span><span class=\"ow\">::<\/span><span class=\"w\"> <\/span><span class=\"kt\">Html<\/span><span class=\"w\"> <\/span><span class=\"nb\">()<\/span>\n<span class=\"nf\">sChatHTMLHandler<\/span><span class=\"w\"> <\/span><span class=\"ow\">=<\/span><span class=\"w\"> <\/span><span class=\"kr\">do<\/span>\n<span class=\"w\">  <\/span><span class=\"n\">doctypehtml_<\/span><span class=\"w\"> <\/span><span class=\"o\">$<\/span><span class=\"w\"> <\/span><span class=\"kr\">do<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">head_<\/span><span class=\"w\"> <\/span><span class=\"o\">$<\/span><span class=\"w\"> <\/span><span class=\"kr\">do<\/span>\n<span class=\"w\">      <\/span><span class=\"n\">title_<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;Simple WebSocket Chat &quot;<\/span>\n<span class=\"w\">      <\/span><span class=\"n\">meta_<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"n\">name_<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;viewport&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">content_<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;width=device-width, initial-scale=1.0&quot;<\/span><span class=\"p\">]<\/span>\n<span class=\"w\">      <\/span><span class=\"n\">xstaticScripts<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"kt\">XStatic<\/span><span class=\"o\">.<\/span><span class=\"n\">htmx<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"kt\">XStatic<\/span><span class=\"o\">.<\/span><span class=\"n\">tailwind<\/span><span class=\"p\">]<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">body_<\/span><span class=\"w\"> <\/span><span class=\"o\">$<\/span><span class=\"w\"> <\/span><span class=\"kr\">do<\/span>\n<span class=\"w\">      <\/span><span class=\"n\">div_<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"n\">class_<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;container mx-auto&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">makeAttribute<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;hx-ws&quot;<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;connect:\/ws&quot;<\/span><span class=\"p\">]<\/span><span class=\"w\"> <\/span><span class=\"o\">$<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">div_<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"n\">id_<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;schat&quot;<\/span><span class=\"p\">]<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;&quot;<\/span>\n<\/pre><\/div>\n<p>This function defines the HTML content to be returned to the client\nafter a connection on &quot;\/&quot;.<\/p>\n<p>The head includes <tt class=\"docutils literal\">script<\/tt> tags to tell the browser to load htmx and\nTailwindcss.<\/p>\n<p>The body defines a <tt class=\"docutils literal\">div<\/tt> with the <tt class=\"docutils literal\"><span class=\"pre\">hx-ws<\/span><\/tt> attribute to tell htmx to\nconnect on the <tt class=\"docutils literal\">\/ws<\/tt> backend's endpoint. See <a class=\"reference external\" href=\"https:\/\/htmx.org\/attributes\/hx-ws\/\">hx-ws<\/a> for details.<\/p>\n<p>Notice the <tt class=\"docutils literal\"><span class=\"pre\">id=&quot;schat&quot;<\/span><\/tt> attribute, the backend will reference it to\nupdate the client's DOM.<\/p>\n<p>When the client's browser open sChat, the first and only thing it does\nis to connect to the Web Socket.<\/p>\n<\/div>\n<div class=\"section\" id=\"the-ws-websocket-handler\">\n<h3>The '\/ws' WebSocket handler<\/h3>\n<div class=\"highlight\"><pre><span><\/span><span class=\"c1\">-- This function loops until the client disconnect<\/span>\n<span class=\"nf\">wsChatHandler<\/span><span class=\"w\"> <\/span><span class=\"ow\">::<\/span><span class=\"w\"> <\/span><span class=\"kt\">SChatS<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">WS<\/span><span class=\"o\">.<\/span><span class=\"kt\">Connection<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">Handler<\/span><span class=\"w\"> <\/span><span class=\"nb\">()<\/span>\n<span class=\"nf\">wsChatHandler<\/span><span class=\"w\"> <\/span><span class=\"n\">state<\/span><span class=\"w\"> <\/span><span class=\"n\">conn<\/span><span class=\"w\"> <\/span><span class=\"ow\">=<\/span><span class=\"w\"> <\/span><span class=\"kr\">do<\/span>\n<span class=\"w\">  <\/span><span class=\"n\">liftIO<\/span><span class=\"w\"> <\/span><span class=\"o\">$<\/span><span class=\"w\"> <\/span><span class=\"kt\">WS<\/span><span class=\"o\">.<\/span><span class=\"n\">withPingThread<\/span><span class=\"w\"> <\/span><span class=\"n\">conn<\/span><span class=\"w\"> <\/span><span class=\"mi\">5<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">pure<\/span><span class=\"w\"> <\/span><span class=\"nb\">()<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"o\">$<\/span><span class=\"w\"> <\/span><span class=\"kr\">do<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">-- Send the rest of WEB UI to the client<\/span>\n<span class=\"w\">    <\/span><span class=\"kt\">WS<\/span><span class=\"o\">.<\/span><span class=\"n\">sendTextData<\/span><span class=\"w\"> <\/span><span class=\"n\">conn<\/span><span class=\"w\"> <\/span><span class=\"o\">$<\/span><span class=\"w\"> <\/span><span class=\"n\">renderBS<\/span><span class=\"w\"> <\/span><span class=\"n\">renderSChat<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">-- Handle the client<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">handleClient<\/span>\n\n<span class=\"c1\">-- The WEB app UI<\/span>\n<span class=\"nf\">renderSChat<\/span><span class=\"w\"> <\/span><span class=\"ow\">::<\/span><span class=\"w\"> <\/span><span class=\"kt\">Html<\/span><span class=\"w\"> <\/span><span class=\"nb\">()<\/span>\n<span class=\"nf\">renderSChat<\/span><span class=\"w\"> <\/span><span class=\"ow\">=<\/span><span class=\"w\"> <\/span><span class=\"kr\">do<\/span>\n<span class=\"w\">  <\/span><span class=\"n\">div_<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"n\">id_<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;schat&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">class_<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;h-auto&quot;<\/span><span class=\"p\">]<\/span><span class=\"w\"> <\/span><span class=\"o\">$<\/span><span class=\"w\"> <\/span><span class=\"kr\">do<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">div_<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"n\">class_<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;bg-purple-100 border-4 border-purple-300 w-full h-full&quot;<\/span><span class=\"p\">]<\/span><span class=\"w\"> <\/span><span class=\"o\">$<\/span><span class=\"w\"> <\/span><span class=\"kr\">do<\/span>\n<span class=\"w\">      <\/span><span class=\"n\">title<\/span>\n<span class=\"w\">      <\/span><span class=\"n\">chatInput<\/span><span class=\"w\"> <\/span><span class=\"kt\">Nothing<\/span>\n<span class=\"w\">      <\/span><span class=\"n\">chatDisplay<\/span>\n<span class=\"w\">      <\/span><span class=\"n\">chatNotices<\/span>\n<span class=\"w\">  <\/span><span class=\"kr\">where<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">title<\/span><span class=\"w\"> <\/span><span class=\"ow\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">p_<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"n\">class_<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;mb-2 pb-1 bg-purple-300 text-xl&quot;<\/span><span class=\"p\">]<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;Simple WebSocket Chat&quot;<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">chatDisplay<\/span><span class=\"w\"> <\/span><span class=\"ow\">=<\/span><span class=\"w\"> <\/span><span class=\"kr\">do<\/span>\n<span class=\"w\">      <\/span><span class=\"n\">div_<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"n\">id_<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;chatroom&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">class_<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;flex flex-row space-x-2 mx-2 my-2 h-96&quot;<\/span><span class=\"p\">]<\/span><span class=\"w\"> <\/span><span class=\"o\">$<\/span><span class=\"w\"> <\/span><span class=\"kr\">do<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">roomChat<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">roomMembers<\/span>\n<span class=\"w\">      <\/span><span class=\"kr\">where<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">roomChat<\/span><span class=\"w\"> <\/span><span class=\"ow\">=<\/span><span class=\"w\"> <\/span><span class=\"kr\">do<\/span>\n<span class=\"w\">          <\/span><span class=\"n\">div_<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"n\">id_<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;chatroom-chat&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">class_<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;flex-auto w-3\/4 h-full&quot;<\/span><span class=\"p\">]<\/span><span class=\"w\"> <\/span><span class=\"o\">$<\/span><span class=\"w\"> <\/span><span class=\"kr\">do<\/span>\n<span class=\"w\">            <\/span><span class=\"n\">div_<\/span>\n<span class=\"w\">              <\/span><span class=\"p\">[<\/span><span class=\"w\"> <\/span><span class=\"n\">id_<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;chatroom-content&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">                <\/span><span class=\"n\">class_<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;overflow-auto border-2 border-purple-200 h-full max-h-full&quot;<\/span>\n<span class=\"w\">              <\/span><span class=\"p\">]<\/span>\n<span class=\"w\">              <\/span><span class=\"s\">&quot;&quot;<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">roomMembers<\/span><span class=\"w\"> <\/span><span class=\"ow\">=<\/span><span class=\"w\"> <\/span><span class=\"kr\">do<\/span>\n<span class=\"w\">          <\/span><span class=\"n\">div_<\/span>\n<span class=\"w\">            <\/span><span class=\"p\">[<\/span><span class=\"w\"> <\/span><span class=\"n\">id_<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;chatroom-members&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">              <\/span><span class=\"n\">class_<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;overflow-auto border-2 border-purple-200 flex-auto w-1\/4 h-full max-h-full&quot;<\/span>\n<span class=\"w\">            <\/span><span class=\"p\">]<\/span>\n<span class=\"w\">            <\/span><span class=\"s\">&quot;&quot;<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">chatNotices<\/span><span class=\"w\"> <\/span><span class=\"ow\">=<\/span><span class=\"w\"> <\/span><span class=\"kr\">do<\/span>\n<span class=\"w\">      <\/span><span class=\"n\">div_<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">[<\/span><span class=\"w\"> <\/span><span class=\"n\">id_<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;chatroom-notices&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">          <\/span><span class=\"n\">class_<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;overflow-auto mb-2 mx-2 border-2 border-purple-200 h-16 max-h-full&quot;<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">]<\/span>\n<span class=\"w\">        <\/span><span class=\"o\">$<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;&quot;<\/span>\n\n<span class=\"c1\">--  The chat&#39;s input field<\/span>\n<span class=\"nf\">chatInput<\/span><span class=\"w\"> <\/span><span class=\"ow\">::<\/span><span class=\"w\"> <\/span><span class=\"kt\">Maybe<\/span><span class=\"w\"> <\/span><span class=\"kt\">Text<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">Html<\/span><span class=\"w\"> <\/span><span class=\"nb\">()<\/span>\n<span class=\"nf\">chatInput<\/span><span class=\"w\"> <\/span><span class=\"n\">loginM<\/span><span class=\"w\"> <\/span><span class=\"ow\">=<\/span><span class=\"w\"> <\/span><span class=\"kr\">do<\/span>\n<span class=\"w\">  <\/span><span class=\"kr\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">inputFieldName<\/span><span class=\"w\"> <\/span><span class=\"ow\">=<\/span><span class=\"w\"> <\/span><span class=\"kr\">if<\/span><span class=\"w\"> <\/span><span class=\"n\">isJust<\/span><span class=\"w\"> <\/span><span class=\"n\">loginM<\/span><span class=\"w\"> <\/span><span class=\"kr\">then<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;chatInputMessage&quot;<\/span><span class=\"w\"> <\/span><span class=\"kr\">else<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;chatInputName&quot;<\/span>\n<span class=\"w\">  <\/span><span class=\"kr\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">inputFieldPlaceholder<\/span><span class=\"w\"> <\/span><span class=\"ow\">=<\/span><span class=\"w\"> <\/span><span class=\"kr\">if<\/span><span class=\"w\"> <\/span><span class=\"n\">isJust<\/span><span class=\"w\"> <\/span><span class=\"n\">loginM<\/span><span class=\"w\"> <\/span><span class=\"kr\">then<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;Enter a message&quot;<\/span><span class=\"w\"> <\/span><span class=\"kr\">else<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;Enter your name&quot;<\/span>\n<span class=\"w\">  <\/span><span class=\"c1\">-- hx-ws attribute tells HTMX to send a payload on the WebSocket when the form is submitted<\/span>\n<span class=\"w\">  <\/span><span class=\"n\">form_<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"n\">hxWS<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;send:submit&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">id_<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;chatroom-input&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">hxSwapOOB<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;innerHTML&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">class_<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;mx-2 bg-purple-200 rounded-lg&quot;<\/span><span class=\"p\">]<\/span><span class=\"w\"> <\/span><span class=\"o\">$<\/span><span class=\"w\"> <\/span><span class=\"kr\">do<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">span_<\/span><span class=\"w\"> <\/span><span class=\"o\">$<\/span><span class=\"w\"> <\/span><span class=\"kr\">do<\/span>\n<span class=\"w\">      <\/span><span class=\"n\">maybe<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">span_<\/span><span class=\"w\"> <\/span><span class=\"kt\">[]<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;&quot;<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"nf\">\\<\/span><span class=\"n\">login<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"n\">span_<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"n\">class_<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;pl-1 pr-2&quot;<\/span><span class=\"p\">]<\/span><span class=\"w\"> <\/span><span class=\"o\">$<\/span><span class=\"w\"> <\/span><span class=\"n\">toHtml<\/span><span class=\"w\"> <\/span><span class=\"n\">login<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"n\">loginM<\/span>\n<span class=\"w\">      <\/span><span class=\"n\">input_<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">[<\/span><span class=\"w\"> <\/span><span class=\"n\">type_<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;text&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">          <\/span><span class=\"n\">class_<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;text-sm rounded-lg bg-purple-50 border border-purple-300 focus:border-purple-400&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">          <\/span><span class=\"c1\">-- The payload sent by HTMX can be identified via the name attribute<\/span>\n<span class=\"w\">          <\/span><span class=\"n\">name_<\/span><span class=\"w\"> <\/span><span class=\"n\">inputFieldName<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">          <\/span><span class=\"n\">id_<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;chatroom-input-field&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">          <\/span><span class=\"n\">placeholder_<\/span><span class=\"w\"> <\/span><span class=\"n\">inputFieldPlaceholder<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">]<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">-- Ensure the field got the focus<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">script_<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;htmx.find(&#39;#chatroom-input-field&#39;).focus()&quot;<\/span>\n<\/pre><\/div>\n<p>As soon as a new WS connection is established we enter in the\n<tt class=\"docutils literal\">wsChatHandler<\/tt> handler function.<\/p>\n<p>First, <tt class=\"docutils literal\">wsChatHandler<\/tt> sends the application UI as defined by\n<tt class=\"docutils literal\">renderSChat<\/tt> on the WS. <tt class=\"docutils literal\">renderSchat<\/tt> defines the following UI\nblocks:<\/p>\n<ul class=\"simple\">\n<li>The title<\/li>\n<li>The input field that the user will use to enter a login name and send\nmessages<\/li>\n<li>The chat content block to display chat' messages<\/li>\n<li>The notice block to display notice' messages (like user connected,\n...)<\/li>\n<li>The room members block to display connected clients<\/li>\n<\/ul>\n<p>Some HTML tags own an id attribute mainly for htmx to be able to <a class=\"reference external\" href=\"https:\/\/htmx.org\/attributes\/hx-swap-oob\/\">swap<\/a>\nthe content based on the payload send back by the backend to the\nbrowser.<\/p>\n<p>Furthermore we add some Tailwindcss classes to prettify the UI.<\/p>\n<p>Here are the first bytes received from the backend by the client over\nthe WS:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"p\">&lt;<\/span><span class=\"nt\">div<\/span> <span class=\"na\">id<\/span><span class=\"o\">=<\/span><span class=\"s\">&quot;schat&quot;<\/span> <span class=\"na\">class<\/span><span class=\"o\">=<\/span><span class=\"s\">&quot;h-auto&quot;<\/span><span class=\"p\">&gt;&lt;<\/span><span class=\"nt\">div<\/span> <span class=\"na\">class<\/span><span class=\"o\">=<\/span><span class=\"s\">&quot;bg-purple-100<\/span>  <span class=\"err\">...<\/span>\n<\/pre><\/div>\n<p>As you can see, this is just plain HTML content. htmx swaps the content\nof the <tt class=\"docutils literal\">chat<\/tt> div on DOM by the content received from the backend. At\nthat point the UI on the browser is fully rendered.<\/p>\n<div class=\"section\" id=\"handling-the-client-login\">\n<h4>Handling the client login<\/h4>\n<p>The <tt class=\"docutils literal\">renderSchat<\/tt> function renders an <tt class=\"docutils literal\">input<\/tt> field with a\n<tt class=\"docutils literal\">chatInputMessage<\/tt>'s name attribute. The parent's <tt class=\"docutils literal\">form<\/tt>\n(<tt class=\"docutils literal\"><span class=\"pre\">chatroom-input<\/span><\/tt>) set an attribute <tt class=\"docutils literal\"><span class=\"pre\">hx-ws:<\/span> &quot;send:submit&quot;<\/tt>.<\/p>\n<p>When the <tt class=\"docutils literal\">form<\/tt> is validated the following JSON payload is sent over\nthe WebSocket by htmx.<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"p\">{<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">&quot;chatInputName&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Fabien&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">&quot;HEADERS&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">&quot;HX-Request&quot;<\/span><span class=\"p\">:<\/span><span class=\"s2\">&quot;true&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">&quot;HX-Trigger&quot;<\/span><span class=\"p\">:<\/span><span class=\"s2\">&quot;chatroom-input&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">&quot;HX-Trigger-Name&quot;<\/span><span class=\"p\">:<\/span><span class=\"kc\">null<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">&quot;HX-Target&quot;<\/span><span class=\"p\">:<\/span><span class=\"kc\">null<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">&quot;HX-Current-URL&quot;<\/span><span class=\"p\">:<\/span><span class=\"s2\">&quot;http:\/\/127.0.0.1:8091\/&quot;<\/span>\n<span class=\"w\">  <\/span><span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<p>Our backend needs to react to that payload. For sChat, we need to wait\nfor such payload in order to validate the new client login. To do so,\nthe <tt class=\"docutils literal\">waitForLoginPayload<\/tt> function blocks until a payload with a key\nname <tt class=\"docutils literal\">chatInputName<\/tt> is received on the WS. Then, the function returns\nthe login to the caller function.<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"nf\">waitForLoginPayload<\/span><span class=\"w\"> <\/span><span class=\"ow\">::<\/span><span class=\"w\"> <\/span><span class=\"kt\">IO<\/span><span class=\"w\"> <\/span><span class=\"kt\">Text<\/span>\n<span class=\"nf\">waitForLoginPayload<\/span><span class=\"w\"> <\/span><span class=\"ow\">=<\/span><span class=\"w\"> <\/span><span class=\"kr\">do<\/span>\n<span class=\"w\">  <\/span><span class=\"c1\">-- Wait until the an input name<\/span>\n<span class=\"w\">  <\/span><span class=\"n\">dataMessage<\/span><span class=\"w\"> <\/span><span class=\"ow\">&lt;-<\/span><span class=\"w\"> <\/span><span class=\"kt\">WS<\/span><span class=\"o\">.<\/span><span class=\"n\">receiveDataMessage<\/span><span class=\"w\"> <\/span><span class=\"n\">conn<\/span>\n<span class=\"w\">  <\/span><span class=\"kr\">case<\/span><span class=\"w\"> <\/span><span class=\"n\">extractMessage<\/span><span class=\"w\"> <\/span><span class=\"n\">dataMessage<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;chatInputName&quot;<\/span><span class=\"w\"> <\/span><span class=\"kr\">of<\/span>\n<span class=\"w\">    <\/span><span class=\"kt\">Just<\/span><span class=\"w\"> <\/span><span class=\"n\">login<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"n\">pure<\/span><span class=\"w\"> <\/span><span class=\"n\">login<\/span>\n<span class=\"w\">    <\/span><span class=\"kt\">Nothing<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"n\">waitForLoginPayload<\/span>\n\n<span class=\"nf\">extractMessage<\/span><span class=\"w\"> <\/span><span class=\"ow\">::<\/span><span class=\"w\"> <\/span><span class=\"kt\">WS<\/span><span class=\"o\">.<\/span><span class=\"kt\">DataMessage<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">Text<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">Maybe<\/span><span class=\"w\"> <\/span><span class=\"kt\">Text<\/span>\n<span class=\"nf\">extractMessage<\/span><span class=\"w\"> <\/span><span class=\"n\">dataMessage<\/span><span class=\"w\"> <\/span><span class=\"n\">keyName<\/span><span class=\"w\"> <\/span><span class=\"ow\">=<\/span>\n<span class=\"w\">  <\/span><span class=\"kr\">case<\/span><span class=\"w\"> <\/span><span class=\"n\">dataMessage<\/span><span class=\"w\"> <\/span><span class=\"kr\">of<\/span>\n<span class=\"w\">    <\/span><span class=\"kt\">WS<\/span><span class=\"o\">.<\/span><span class=\"kt\">Text<\/span><span class=\"w\"> <\/span><span class=\"n\">bs<\/span><span class=\"w\"> <\/span><span class=\"kr\">_<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"kr\">do<\/span>\n<span class=\"w\">      <\/span><span class=\"kr\">case<\/span><span class=\"w\"> <\/span><span class=\"n\">bs<\/span><span class=\"w\"> <\/span><span class=\"o\">^?<\/span><span class=\"w\"> <\/span><span class=\"n\">key<\/span><span class=\"w\"> <\/span><span class=\"n\">keyName<\/span><span class=\"w\"> <\/span><span class=\"kr\">of<\/span>\n<span class=\"w\">        <\/span><span class=\"kt\">Just<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"kt\">String<\/span><span class=\"w\"> <\/span><span class=\"n\">m<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">Just<\/span><span class=\"w\"> <\/span><span class=\"n\">m<\/span>\n<span class=\"w\">        <\/span><span class=\"kr\">_<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">Nothing<\/span>\n<span class=\"w\">    <\/span><span class=\"kr\">_<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">Nothing<\/span>\n<\/pre><\/div>\n<p>After the client's login we want to:<\/p>\n<ul class=\"simple\">\n<li>refresh the input <tt class=\"docutils literal\">form<\/tt><\/li>\n<li>display the login name in front of the <tt class=\"docutils literal\">input<\/tt> field<\/li>\n<li>change the <tt class=\"docutils literal\">input<\/tt> placeholder text<\/li>\n<\/ul>\n<p>To do that, we simply send a new <tt class=\"docutils literal\">form<\/tt> (using the <tt class=\"docutils literal\">renderInputChat<\/tt>\nfunction) to the client via the WS and rely on the <a class=\"reference external\" href=\"https:\/\/htmx.org\/attributes\/hx-swap-oob\/\">swap<\/a> feature to\nget the form updated. Note that, we use a bit of Javascript to ensure\nthat the <tt class=\"docutils literal\">input<\/tt> field get the focus.<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"w\">  <\/span><span class=\"o\">...<\/span>\n<span class=\"w\">  <\/span><span class=\"n\">loginE<\/span><span class=\"w\"> <\/span><span class=\"ow\">&lt;-<\/span><span class=\"w\"> <\/span><span class=\"n\">tryAny<\/span><span class=\"w\"> <\/span><span class=\"n\">waitForLogin<\/span>\n<span class=\"w\">  <\/span><span class=\"kr\">case<\/span><span class=\"w\"> <\/span><span class=\"n\">loginE<\/span><span class=\"w\"> <\/span><span class=\"kr\">of<\/span>\n<span class=\"w\">    <\/span><span class=\"kt\">Right<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"kt\">Just<\/span><span class=\"w\"> <\/span><span class=\"n\">client<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"kr\">do<\/span>\n<span class=\"w\">      <\/span><span class=\"c1\">-- Replace the input login box with the input message box<\/span>\n<span class=\"w\">      <\/span><span class=\"kt\">WS<\/span><span class=\"o\">.<\/span><span class=\"n\">sendTextData<\/span><span class=\"w\"> <\/span><span class=\"n\">conn<\/span><span class=\"w\"> <\/span><span class=\"o\">$<\/span><span class=\"w\"> <\/span><span class=\"n\">renderInputChat<\/span><span class=\"w\"> <\/span><span class=\"n\">client<\/span><span class=\"o\">.<\/span><span class=\"n\">cLogin<\/span>\n<span class=\"w\">      <\/span><span class=\"c1\">-- Start handling the acknowledged client<\/span>\n<span class=\"w\">      <\/span><span class=\"n\">handleConnected<\/span><span class=\"w\"> <\/span><span class=\"n\">client<\/span>\n<span class=\"w\">  <\/span><span class=\"o\">...<\/span>\n\n<span class=\"c1\">-- Helper to render the chat&#39;s input field<\/span>\n<span class=\"nf\">renderInputChat<\/span><span class=\"w\"> <\/span><span class=\"ow\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">renderBS<\/span><span class=\"w\"> <\/span><span class=\"o\">.<\/span><span class=\"w\"> <\/span><span class=\"n\">chatInput<\/span><span class=\"w\"> <\/span><span class=\"o\">.<\/span><span class=\"w\"> <\/span><span class=\"kt\">Just<\/span>\n<\/pre><\/div>\n<\/div>\n<div class=\"section\" id=\"handling-client-messages\">\n<h4>Handling client messages<\/h4>\n<p>Handling messages (input and rendering) follows the same technic as of\nwaiting for a message input payload on the WS and updating the client\nDOM via the WS.<\/p>\n<p>After a client is 'connected', sChat starts two threads:<\/p>\n<ul class=\"simple\">\n<li>a <tt class=\"docutils literal\">recv<\/tt> thread to process any message payload appearing on the WS.<\/li>\n<li>a <tt class=\"docutils literal\">send<\/tt> thread to dispatch the right HTML payload via the WS to\nthe client.<\/li>\n<\/ul>\n<p>When a message payload appears on the WS then the <tt class=\"docutils literal\">recv<\/tt> thread calls\nthe <tt class=\"docutils literal\">extractMessage<\/tt> function and creates an <tt class=\"docutils literal\">EMessage<\/tt> data that is\nsent to all connected client's queue. Then the <tt class=\"docutils literal\">send<\/tt> thread reads the\nqueue and sends back the right payload via the WS to the client.<\/p>\n<p>The <tt class=\"docutils literal\">EMessage<\/tt> is rendered using the <tt class=\"docutils literal\">afterbegin<\/tt>\n<a class=\"reference external\" href=\"https:\/\/htmx.org\/attributes\/hx-swap\/\">swap<\/a> method. Which means that\nwe insert the response before previous <tt class=\"docutils literal\"><span class=\"pre\">chatroom-message<\/span><\/tt> elements.<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"o\">...<\/span>\n<span class=\"kr\">case<\/span><span class=\"w\"> <\/span><span class=\"n\">extractMessage<\/span><span class=\"w\"> <\/span><span class=\"n\">wsD<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;chatInputMessage&quot;<\/span><span class=\"w\"> <\/span><span class=\"kr\">of<\/span>\n<span class=\"w\">  <\/span><span class=\"kt\">Just<\/span><span class=\"w\"> <\/span><span class=\"n\">inputMsg<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"kr\">do<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">now<\/span><span class=\"w\"> <\/span><span class=\"ow\">&lt;-<\/span><span class=\"w\"> <\/span><span class=\"n\">getCurrentTime<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">dispatchToAll<\/span><span class=\"w\"> <\/span><span class=\"o\">$<\/span><span class=\"w\"> <\/span><span class=\"kt\">EMessage<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"kt\">Message<\/span><span class=\"w\"> <\/span><span class=\"n\">now<\/span><span class=\"w\"> <\/span><span class=\"n\">myLogin<\/span><span class=\"w\"> <\/span><span class=\"n\">inputMsg<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">  <\/span><span class=\"kt\">Nothing<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"n\">pure<\/span><span class=\"w\"> <\/span><span class=\"nb\">()<\/span>\n<span class=\"o\">...<\/span>\n<\/pre><\/div>\n<div class=\"highlight\"><pre><span><\/span><span class=\"nf\">renderMessage<\/span><span class=\"w\"> <\/span><span class=\"ow\">::<\/span><span class=\"w\"> <\/span><span class=\"kt\">Message<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">Html<\/span><span class=\"w\"> <\/span><span class=\"nb\">()<\/span>\n<span class=\"nf\">renderMessage<\/span><span class=\"w\"> <\/span><span class=\"n\">msg<\/span><span class=\"w\"> <\/span><span class=\"ow\">=<\/span><span class=\"w\"> <\/span><span class=\"kr\">do<\/span>\n<span class=\"w\">  <\/span><span class=\"c1\">-- The id and hx-swap-oob tell HTMX which elements to update in the DOM<\/span>\n<span class=\"w\">  <\/span><span class=\"n\">div_<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"n\">id_<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;chatroom-content&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">hxSwapOOB<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;afterbegin&quot;<\/span><span class=\"p\">]<\/span><span class=\"w\"> <\/span><span class=\"o\">$<\/span><span class=\"w\"> <\/span><span class=\"kr\">do<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">div_<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"n\">id_<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;chatroom-message&quot;<\/span><span class=\"p\">]<\/span><span class=\"w\"> <\/span><span class=\"o\">$<\/span><span class=\"w\"> <\/span><span class=\"kr\">do<\/span>\n<span class=\"w\">      <\/span><span class=\"n\">span_<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"n\">id_<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;chatroom-message-date&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">class_<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;pr-2&quot;<\/span><span class=\"p\">]<\/span><span class=\"w\"> <\/span><span class=\"o\">.<\/span><span class=\"w\"> <\/span><span class=\"n\">toHtml<\/span><span class=\"w\"> <\/span><span class=\"o\">$<\/span><span class=\"w\"> <\/span><span class=\"n\">formatDate<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">msg<\/span><span class=\"o\">.<\/span><span class=\"n\">date<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">      <\/span><span class=\"n\">span_<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"n\">id_<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;chatroom-message-login&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">class_<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;pr-2&quot;<\/span><span class=\"p\">]<\/span><span class=\"w\"> <\/span><span class=\"o\">.<\/span><span class=\"w\"> <\/span><span class=\"n\">toHtml<\/span><span class=\"w\"> <\/span><span class=\"o\">$<\/span><span class=\"w\"> <\/span><span class=\"n\">unpack<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">msg<\/span><span class=\"o\">.<\/span><span class=\"n\">mLogin<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">      <\/span><span class=\"n\">span_<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"n\">id_<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;chatroom-message-content&quot;<\/span><span class=\"p\">]<\/span><span class=\"w\"> <\/span><span class=\"o\">.<\/span><span class=\"w\"> <\/span><span class=\"n\">toHtml<\/span><span class=\"w\"> <\/span><span class=\"o\">$<\/span><span class=\"w\"> <\/span><span class=\"n\">unpack<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">msg<\/span><span class=\"o\">.<\/span><span class=\"n\">content<\/span><span class=\"p\">)<\/span>\n<\/pre><\/div>\n<p>We wont go over the <tt class=\"docutils literal\"><span class=\"pre\">chatroom-members<\/span><\/tt> and <tt class=\"docutils literal\"><span class=\"pre\">chat-notices<\/span><\/tt> divs\nupdate because they are updated using the same htmx's swap technic.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"section\" id=\"to-conclude\">\n<h2>To conclude<\/h2>\n<p>As you can seen, using htmx and its WebSocket feature, we were able to\nbuild a dynamic WEB UI for our chat application without the need:<\/p>\n<ul class=\"simple\">\n<li>to build a complex REST API.<\/li>\n<li>to write Javascript client code to perform I\/O with our backend and\nupdate the client DOM.<\/li>\n<li>to use two different toolchains for the backend and UI.<\/li>\n<\/ul>\n<p>Htmx also supports regular HTTP target (GET, POST, ...) which are more\nadapted for traditional WEB applications.<\/p>\n<p>Personnaly, I feel really happy to have learnt a bit about HTMX and got\nthe ability to write pretty quickly decent WEB frontends for server side\napplications. Next, I'll attempt to use htmx in more complex and dynamic\napplications.<\/p>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2022 Aug 26 to Sep 14 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2022-aug-26-to-sep-14-summary.html","rel":"alternate"}},"published":"2022-09-14T10:00:00+00:00","updated":"2022-09-14T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2022-09-14:\/sprint-2022-aug-26-to-sep-14-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We added Elasticsearch exporter<\/li>\n<li>We investigated using opentelemtry python SDK for the Zuul tracing specification<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<div class=\"section\" id=\"next-sf-release-sf-3-8\">\n<h3>next sf release (sf-3.8)<\/h3>\n<ul class=\"simple\">\n<li>We containerized the purgelogs service.<\/li>\n<li>We added zuul-fingergw service on sf-zuul role for zuul-console<\/li>\n<li>We fixed \u2026<\/li><\/ul><\/div><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We added Elasticsearch exporter<\/li>\n<li>We investigated using opentelemtry python SDK for the Zuul tracing specification<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<div class=\"section\" id=\"next-sf-release-sf-3-8\">\n<h3>next sf release (sf-3.8)<\/h3>\n<ul class=\"simple\">\n<li>We containerized the purgelogs service.<\/li>\n<li>We added zuul-fingergw service on sf-zuul role for zuul-console<\/li>\n<li>We fixed issue where zuul and nodepool services are not enabled during sf-config run<\/li>\n<li>We containerized Logserver and Managesf SF services<\/li>\n<li>We updated Opensearch and Opensearch Dashboards services to newest version<\/li>\n<li>We have worked on the migration path from cauth to keycloak<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"sf-operator-sf-4-0\">\n<h3>SF-operator (sf-4.0)<\/h3>\n<ul class=\"simple\">\n<li>We've added the setup for Gerrit to authenticate via Keycloak<\/li>\n<li>We've added the setup to handle SSH key fetching via KC to Gerrit user account<\/li>\n<li>We added initial system config to the sf-operator to be able to trigger test job.<\/li>\n<li>We added a jaeger service to manage telemetry<\/li>\n<li>We added a develop mode to restart the service using local source.<\/li>\n<li>We added Lodgeit K8s Operator and started on Managesf<\/li>\n<\/ul>\n<\/div>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2022 Aug 05 to Aug 24 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2022-aug-05-to-aug-24-summary.html","rel":"alternate"}},"published":"2022-08-24T10:00:00+00:00","updated":"2022-08-24T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2022-08-24:\/sprint-2022-aug-05-to-aug-24-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>ci-log-processing: We fixed issue related to wrong parsing performance.json file<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We containerized the github ssh key updater middleware and are working on deploying it along keycloak.<\/li>\n<li>We have created Lodgeit, Murmur and Mosquitto SF services \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>ci-log-processing: We fixed issue related to wrong parsing performance.json file<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We containerized the github ssh key updater middleware and are working on deploying it along keycloak.<\/li>\n<li>We have created Lodgeit, Murmur and Mosquitto SF services for sf-operator<\/li>\n<li>We containerized Mosquitto SF services<\/li>\n<li>We added a standalone mode to the sf-operator to enable deploying resources without running kubectl apply first.<\/li>\n<li>We updated zuul and nodepool to the latest version in sf-config<\/li>\n<li>We updated the zuul-weeder service to better display periodic pipelines, see <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/weeder\/tenant\/rdoproject.org\/info\">https:\/\/softwarefactory-project.io\/weeder\/tenant\/rdoproject.org\/info<\/a><\/li>\n<li>We finally finished renaming elk stack to opensearch<\/li>\n<li>We added diskclass resource that operator can be running on kubernetes not deployed on kind<\/li>\n<li>We improved opensearch and opensearch dashboards in sf-operators<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2022 Jul 15 to Aug 03 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2022-jul-15-to-aug-03-summary.html","rel":"alternate"}},"published":"2022-08-03T10:00:00+00:00","updated":"2022-08-03T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2022-08-03:\/sprint-2022-jul-15-to-aug-03-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We reviewed the pending Zuul spec.<\/li>\n<li>We added prometheus probe to the Zuul operator<\/li>\n<li>we worked on zuul web API error pages<\/li>\n<li>We worked on fixing logsender performance.json file due there was some changes in performance.json \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We reviewed the pending Zuul spec.<\/li>\n<li>We added prometheus probe to the Zuul operator<\/li>\n<li>we worked on zuul web API error pages<\/li>\n<li>We worked on fixing logsender performance.json file due there was some changes in performance.json file and it fails on sending it to the Opensearch<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We started to integrate the config-update job into the sf-operator<\/li>\n<li>We containerized Hound, Cgit, InfluxDB and LogServer<\/li>\n<li>We are currently developing sf-operator and added Lodgeit and Murmur SF services<\/li>\n<li>We changed name of elasticsearch role to opensearch<\/li>\n<li>We added opensearch component in sf-operator<\/li>\n<li>We removed logstash service from sf-config project and it has been replaced by sf-log-processing role<\/li>\n<li>we started writing a middleware to synchronize ssh keys between keycloak and gerrit in go<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"How to manually update Kubernetes Resources","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/how-to-manually-update-kubernetes-resources.html","rel":"alternate"}},"published":"2022-07-15T00:00:00+00:00","updated":"2022-07-15T00:00:00+00:00","author":{"name":"tristanC"},"id":"tag:www.softwarefactory-project.io,2022-07-15:\/how-to-manually-update-kubernetes-resources.html","summary":"<p>This article demonstrates different strategies to update kubernetes resources.<\/p>\n<div class=\"section\" id=\"context-and-problem-statement\">\n<h2>Context and Problem Statement<\/h2>\n<p>Our goal is to update resource without overwritting changes made outside of our control.\nFor example, we would like to upgrade a container image version or a deployment replicas count.<\/p>\n<p>In the context of a kubernetes operator \u2026<\/p><\/div>","content":"<p>This article demonstrates different strategies to update kubernetes resources.<\/p>\n<div class=\"section\" id=\"context-and-problem-statement\">\n<h2>Context and Problem Statement<\/h2>\n<p>Our goal is to update resource without overwritting changes made outside of our control.\nFor example, we would like to upgrade a container image version or a deployment replicas count.<\/p>\n<p>In the context of a kubernetes operator, there are two forms of resource update: <em>replace<\/em> and <em>patch<\/em>.\nTo demonstrate how the update process works, we'll use the API directly by starting a proxy:<\/p>\n<div class=\"highlight\"><pre><span><\/span>kubectl<span class=\"w\"> <\/span>proxy<span class=\"w\"> <\/span>--port<span class=\"o\">=<\/span><span class=\"m\">8080<\/span>\n<\/pre><\/div>\n<\/div>\n<div class=\"section\" id=\"resource-version\">\n<h2>Resource Version<\/h2>\n<p>Each resource is assigned a version number in its metadata.\nIn this example, we create a deployment named <em>demo-deployment<\/em> in the namespace <em>tristanc<\/em>:<\/p>\n<div class=\"highlight\"><pre><span><\/span>curl<span class=\"w\"> <\/span>-XPOST<span class=\"w\"> <\/span>-H<span class=\"w\"> <\/span><span class=\"s2\">&quot;Content-Type: application\/json&quot;<\/span><span class=\"w\"> <\/span>http:\/\/localhost:8080\/apis\/apps\/v1\/namespaces\/tristanc\/deployments<span class=\"w\"> <\/span>--data-binary<span class=\"w\"> <\/span>@-<span class=\"w\"> <\/span><span class=\"s\">&lt;&lt;EOF<\/span>\n<span class=\"s\">{<\/span>\n<span class=\"s\">  &quot;metadata&quot;: {&quot;name&quot;: &quot;demo-deployment&quot;},<\/span>\n<span class=\"s\">  &quot;spec&quot;: {<\/span>\n<span class=\"s\">    &quot;replicas&quot;: 1,<\/span>\n<span class=\"s\">    &quot;selector&quot;: {&quot;matchLabels&quot;: {&quot;app&quot;: &quot;demo&quot;}},<\/span>\n<span class=\"s\">    &quot;template&quot;: {<\/span>\n<span class=\"s\">      &quot;metadata&quot;: {&quot;labels&quot;: {&quot;app&quot;: &quot;demo&quot;}},<\/span>\n<span class=\"s\">      &quot;spec&quot;: {<\/span>\n<span class=\"s\">        &quot;containers&quot;: [{<\/span>\n<span class=\"s\">            &quot;name&quot;: &quot;container-1&quot;<\/span>\n<span class=\"s\">            &quot;args&quot;: [&quot;sleep&quot;, &quot;infinity&quot;],<\/span>\n<span class=\"s\">            &quot;image&quot;: &quot;registry.fedoraproject.org\/fedora:latest&quot;,<\/span>\n<span class=\"s\">            &quot;imagePullPolicy&quot;: &quot;IfNotPresent&quot;,<\/span>\n<span class=\"s\">        }]<\/span>\n<span class=\"s\">      }<\/span>\n<span class=\"s\">    }<\/span>\n<span class=\"s\">  }<\/span>\n<span class=\"s\">}<\/span>\n<span class=\"s\">EOF<\/span>\n<\/pre><\/div>\n<p>The API returns the resource state with its <em>resourceVersion<\/em>.\nEach time the resource is updated, the API automatically update the version number.<\/p>\n<p>You can run the update request below using:<\/p>\n<div class=\"highlight\"><pre><span><\/span>curl<span class=\"w\"> <\/span>-XVERB<span class=\"w\"> <\/span>-H<span class=\"w\"> <\/span><span class=\"s2\">&quot;Content-Type: TYPE&quot;<\/span><span class=\"w\"> <\/span>http:\/\/localhost:8080\/apis\/apps\/v1\/namespaces\/tristanc\/deployments\/demo-deployment<span class=\"w\"> <\/span>--data-binary<span class=\"w\"> <\/span>@-<span class=\"w\"> <\/span><span class=\"s\">&lt;&lt;EOF<\/span>\n<span class=\"s\">  BODY<\/span>\n<span class=\"s\">EOF<\/span>\n<\/pre><\/div>\n<\/div>\n<div class=\"section\" id=\"json-patch\">\n<h2>JSON Patch<\/h2>\n<p>A <a class=\"reference external\" href=\"https:\/\/tools.ietf.org\/html\/rfc6902\">JSON Patch RFC 6902<\/a> is defined by a <strong>op<\/strong> operation, <strong>path<\/strong> destination and a <strong>value<\/strong>.<\/p>\n<div class=\"highlight\"><pre><span><\/span>curl<span class=\"w\"> <\/span>-XPATCH<span class=\"w\"> <\/span>-H<span class=\"w\"> <\/span><span class=\"s2\">&quot;Content-Type: application\/json-patch+json&quot;<\/span>\n<\/pre><\/div>\n<div class=\"section\" id=\"add-a-container\">\n<h3>Add a container<\/h3>\n<div class=\"highlight\"><pre><span><\/span><span class=\"p\">[<\/span>\n<span class=\"w\">  <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">&quot;op&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;add&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">&quot;path&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;\/spec\/template\/spec\/containers\/-&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">&quot;value&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">&quot;name&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;container-2&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">&quot;args&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"s2\">&quot;sleep&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;infinity&quot;<\/span><span class=\"p\">],<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">&quot;image&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;registry.fedoraproject.org\/fedora:latest&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">&quot;imagePullPolicy&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;IfNotPresent&quot;<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">  <\/span><span class=\"p\">}<\/span>\n<span class=\"p\">]<\/span>\n<\/pre><\/div>\n<\/div>\n<div class=\"section\" id=\"remove-a-container\">\n<h3>Remove a container<\/h3>\n<p>List indices are zero based.<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"p\">[<\/span>\n<span class=\"w\">  <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">&quot;op&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;remove&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">&quot;path&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;\/spec\/template\/spec\/containers\/1&quot;<\/span>\n<span class=\"w\">  <\/span><span class=\"p\">}<\/span>\n<span class=\"p\">]<\/span>\n<\/pre><\/div>\n<\/div>\n<div class=\"section\" id=\"change-the-image\">\n<h3>Change the image<\/h3>\n<div class=\"highlight\"><pre><span><\/span><span class=\"p\">[<\/span>\n<span class=\"w\">  <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">&quot;op&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;replace&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">&quot;path&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nt\">&quot;\/spec\/template\/spec\/containers\/0\/image&quot;<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">&quot;value&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;registry.fedoraproject.org\/fedora:36&quot;<\/span>\n<span class=\"w\">  <\/span><span class=\"p\">}<\/span>\n<span class=\"p\">]<\/span>\n<\/pre><\/div>\n<\/div>\n<div class=\"section\" id=\"test\">\n<h3>Test<\/h3>\n<p>A JSON Patch can also assert a desired state:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"p\">[<\/span>\n<span class=\"w\">  <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">&quot;op&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;test&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">&quot;path&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;\/spec\/template\/spec\/containers\/0\/image&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">&quot;value&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;registry.fedoraproject.org\/fedora:37&quot;<\/span>\n<span class=\"w\">  <\/span><span class=\"p\">}<\/span>\n<span class=\"p\">]<\/span>\n<\/pre><\/div>\n<\/div>\n<\/div>\n<div class=\"section\" id=\"json-merge-patch\">\n<h2>JSON Merge Patch<\/h2>\n<p>A <a class=\"reference external\" href=\"https:\/\/tools.ietf.org\/html\/rfc7386\">JSON Merge Patch RFC 7396<\/a> is more similar to a diff.\nList elements can't be manipulated and the full list needs to be provided.<\/p>\n<div class=\"highlight\"><pre><span><\/span>curl<span class=\"w\"> <\/span>-XPATCH<span class=\"w\"> <\/span>-H<span class=\"w\"> <\/span><span class=\"s2\">&quot;Content-Type: application\/merge-patch+json&quot;<\/span>\n<\/pre><\/div>\n<div class=\"section\" id=\"change-containers\">\n<h3>Change containers<\/h3>\n<div class=\"highlight\"><pre><span><\/span><span class=\"p\">{<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">&quot;spec&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">&quot;template&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">&quot;spec&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;containers&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">[{<\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;name&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;container-1&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;args&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"s2\">&quot;sleep&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;infinity&quot;<\/span><span class=\"p\">],<\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;image&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;registry.fedoraproject.org\/fedora:latest&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;imagePullPolicy&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;IfNotPresent&quot;<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">},<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;name&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;container-2&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;args&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"s2\">&quot;sleep&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;infinity&quot;<\/span><span class=\"p\">],<\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;image&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;registry.fedoraproject.org\/fedora:latest&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;imagePullPolicy&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;IfNotPresent&quot;<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}]<\/span>\n<span class=\"w\">      <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">  <\/span><span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<\/div>\n<div class=\"section\" id=\"change-replica-count\">\n<h3>Change replica count<\/h3>\n<div class=\"highlight\"><pre><span><\/span><span class=\"p\">{<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">&quot;spec&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">&quot;replicas&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"mi\">2<\/span>\n<span class=\"w\">  <\/span><span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<\/div>\n<\/div>\n<div class=\"section\" id=\"strategic-merge-patch\">\n<h2>Strategic Merge Patch<\/h2>\n<p>A strategic patch is similar to a JSON Merge Patch, but with custom behaviors defined in the OpenAPI.\nFor example, the pod template spec enables adding containers to the list.<\/p>\n<div class=\"highlight\"><pre><span><\/span>curl<span class=\"w\"> <\/span>-XPATCH<span class=\"w\"> <\/span>-H<span class=\"w\"> <\/span><span class=\"s2\">&quot;Content-Type: application\/strategic-merge-patch+json&quot;<\/span>\n<\/pre><\/div>\n<div class=\"section\" id=\"add-a-container-1\">\n<h3>Add a container<\/h3>\n<div class=\"highlight\"><pre><span><\/span><span class=\"p\">{<\/span>\n<span class=\"w\">  <\/span><span class=\"nt\">&quot;spec&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">&quot;template&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">      <\/span><span class=\"nt\">&quot;spec&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;containers&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">[{<\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;name&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;container-2&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;args&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"s2\">&quot;sleep&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;infinity&quot;<\/span><span class=\"p\">],<\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;image&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;registry.fedoraproject.org\/fedora:latest&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;imagePullPolicy&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;IfNotPresent&quot;<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}]<\/span>\n<span class=\"w\">      <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">  <\/span><span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<\/div>\n<\/div>\n<div class=\"section\" id=\"server-side-apply\">\n<h2>Server Side Apply<\/h2>\n<p>Since Kubernetes v1.22, a new option is available when using:<\/p>\n<div class=\"highlight\"><pre><span><\/span>curl<span class=\"w\"> <\/span>-XPATCH<span class=\"w\"> <\/span>-H<span class=\"w\"> <\/span><span class=\"s2\">&quot;Content-Type: application\/apply-patch+yaml&quot;<\/span>\n<\/pre><\/div>\n<p>This feature leverage a new &quot;field management&quot; mechanism, and it seems useful when multiple clients\nare updating a single resource.\nThis feature is fairly new, and it is not yet fully supported by the controller-runtime client.<\/p>\n<\/div>\n<div class=\"section\" id=\"replace\">\n<h2>Replace<\/h2>\n<p>The other solution is to replace the resource:<\/p>\n<div class=\"highlight\"><pre><span><\/span>curl<span class=\"w\"> <\/span>-XPUT<span class=\"w\"> <\/span>-H<span class=\"w\"> <\/span><span class=\"s2\">&quot;Content-Type: application\/json&quot;<\/span>\n<\/pre><\/div>\n<p>The body must contains the full resource, otherwise the request will fail.<\/p>\n<div class=\"section\" id=\"get-and-replace\">\n<h3>Get and replace<\/h3>\n<div class=\"highlight\"><pre><span><\/span>curl<span class=\"w\"> <\/span>http:\/\/localhost:8080\/apis\/apps\/v1\/namespaces\/tristanc\/deployments\/demo-deployment<span class=\"w\"> <\/span>&gt;<span class=\"w\"> <\/span>dep.json\n<span class=\"c1\"># edit the file<\/span>\ncurl<span class=\"w\"> <\/span>-XPUT<span class=\"w\"> <\/span>-H<span class=\"w\"> <\/span><span class=\"s2\">&quot;Content-Type: application\/json&quot;<\/span><span class=\"w\"> <\/span>http:\/\/localhost:8080\/apis\/apps\/v1\/namespaces\/tristanc\/deployments\/demo-deployment<span class=\"w\"> <\/span>-d@dep.json\n<\/pre><\/div>\n<p>Notice that changing the resource results in a new <em>resourceVersion<\/em>, and trying to repeat the last request will fails because the version no longer match.<\/p>\n<\/div>\n<\/div>\n<div class=\"section\" id=\"conclusion\">\n<h2>Conclusion<\/h2>\n<p>Kubernetes provides multiple update strategies to manage resources:<\/p>\n<ul class=\"simple\">\n<li>The easiest option seems to be the Strategic Merge Patch, or the new Server Side Apply, but the resulting update depend on the nature of the change,\nfor example it is not clear how to remove an element from a list.<\/li>\n<li>JSON Patch seems to be the most efficient, but the request body needs to be prepared. JSON Merge Patch is another solution,\nbut removing attributes requires using a <em>null<\/em> value. Learn more about the difference in this <a class=\"reference external\" href=\"https:\/\/erosb.github.io\/post\/json-patch-vs-merge-patch\/\">post<\/a>.<\/li>\n<li>Finally replace seems to be most straightforward solution, but the full resources needs to be known in advance.<\/li>\n<\/ul>\n<p>The controller-runtime provides a convenient Update method to use the replace strategy: <a class=\"reference external\" href=\"https:\/\/pkg.go.dev\/sigs.k8s.io\/controller-runtime\/pkg\/client#example-Client-Update\">Example Client Update<\/a>.\nThis pattern works great in the context of an operator where the state of the resources is usually known before making creation or update request.<\/p>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2022 Jun 24 to Jul 13 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2022-jun-24-to-jul-13-summary.html","rel":"alternate"}},"published":"2022-07-13T10:00:00+00:00","updated":"2022-07-13T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2022-07-13:\/sprint-2022-jun-24-to-jul-13-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We Updated zuul-operator to latest CRD and fixed issues with pykube-ng<\/li>\n<li>We are working on improving API error messages and their handling in the GUI<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We investigated how to integrate the zuul-operator, but it is a \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We Updated zuul-operator to latest CRD and fixed issues with pykube-ng<\/li>\n<li>We are working on improving API error messages and their handling in the GUI<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We investigated how to integrate the zuul-operator, but it is a lot of work<\/li>\n<li>We continued developping the sf-operator, it now deploy keycloak, gerrit, etherpad, zookeeper and zuul<\/li>\n<li>We have sub-released of SF 3.7 <a class=\"reference external\" href=\"https:\/\/www.softwarefactory-project.io\/releases\/3.7\/\">https:\/\/www.softwarefactory-project.io\/releases\/3.7\/<\/a><\/li>\n<li>We have bumped the Gerrit container to 3.4.5 <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/containers\/+\/25360\">https:\/\/softwarefactory-project.io\/r\/c\/containers\/+\/25360<\/a> and <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-config\/+\/25365\/\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-config\/+\/25365\/<\/a><\/li>\n<li>We containerized Murmur and Lodgeit<\/li>\n<li>We started to containerized Hound, Cgit and Gateway<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Hacking Zuul for developers","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/hacking-zuul-for-developers.html","rel":"alternate"}},"published":"2022-07-01T00:00:00+00:00","updated":"2022-07-01T00:00:00+00:00","author":{"name":"mhuin"},"id":"tag:www.softwarefactory-project.io,2022-07-01:\/hacking-zuul-for-developers.html","summary":"<p>Zuul can be, honestly, quite an intimidating beast to handle. Running Zuul\nitself requires setting up many satellite services like mariadb and zookeeper.\nThis might make it hard to quickly test small changes, or just tinker with the code base.<\/p>\n<p>I will share some tips on how to experiment or \u2026<\/p>","content":"<p>Zuul can be, honestly, quite an intimidating beast to handle. Running Zuul\nitself requires setting up many satellite services like mariadb and zookeeper.\nThis might make it hard to quickly test small changes, or just tinker with the code base.<\/p>\n<p>I will share some tips on how to experiment or play around with Zuul's code.<\/p>\n<div class=\"section\" id=\"the-regular-way-zuul-ci\">\n<h2>The regular way: Zuul CI<\/h2>\n<p>When you are putting in the effort to modify Zuul's code base, chances are you would\nlike your change to eventually get merged into <a class=\"reference external\" href=\"https:\/\/opendev.org\/zuul\/zuul\">the upstream repository<\/a>.\nThen you are going to have to get a &quot;Verified +1&quot; review from Zuul's automated CI.<\/p>\n<p>In that case, you might as well just run your tests in the upstream CI. The one major drawback\nthat I see with this, is that the feedback loop can be relatively long: the test suite\nis quite exhaustive, and you are sharing testing resources with other developers from all\nof Opendev. It is not rare to get feedback from the CI only one or two hours after having\npushed your change (and that's honestly not a bad performance at all).<\/p>\n<p>A simple way to speed up the process is to limit your testing to the strict minimum:\nlinters and tox-py*. You could even drop the linters tests as they require very little\ndependencies and can be run in your local dev environment even with limited resources:<\/p>\n<pre class=\"code bash literal-block\">\ntox<span class=\"w\"> <\/span>-elinters\n<\/pre>\n<p>Locate the <cite>.zuul.yaml<\/cite> file at the root of your local copy of the Zuul repository\nand comment out the jobs you don't want to run like so:<\/p>\n<pre class=\"code yaml literal-block\">\n<span class=\"p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">project<\/span><span class=\"p\">:<\/span><span class=\"w\">\n    <\/span><span class=\"nt\">vars<\/span><span class=\"p\">:<\/span><span class=\"w\">\n      <\/span><span class=\"nt\">node_version<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l-Scalar-Plain\">16<\/span><span class=\"w\">\n      <\/span><span class=\"nt\">release_python<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l-Scalar-Plain\">python3<\/span><span class=\"w\">\n    <\/span><span class=\"nt\">check<\/span><span class=\"p\">:<\/span><span class=\"w\">\n      <\/span><span class=\"nt\">jobs<\/span><span class=\"p\">:<\/span><span class=\"w\">\n      <\/span><span class=\"c1\">#  - zuul-build-image<\/span><span class=\"w\">\n      <\/span><span class=\"c1\">#  - zuul-tox-docs<\/span><span class=\"w\">\n        <\/span><span class=\"p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">tox-linters<\/span><span class=\"p\">:<\/span><span class=\"w\">\n            <\/span><span class=\"nt\">vars<\/span><span class=\"p\">:<\/span><span class=\"w\">\n              <\/span><span class=\"nt\">tox_install_bindep<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l-Scalar-Plain\">false<\/span><span class=\"w\">\n        <\/span><span class=\"p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"l-Scalar-Plain\">zuul-tox-py38<\/span><span class=\"w\">\n        <\/span><span class=\"p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"l-Scalar-Plain\">zuul-tox-py39<\/span><span class=\"w\">\n      <\/span><span class=\"c1\">#  - zuul-tox-py39-multi-scheduler<\/span><span class=\"w\">\n      <\/span><span class=\"c1\">#  - zuul-build-dashboard-openstack-whitelabel<\/span><span class=\"w\">\n      <\/span><span class=\"c1\">#  - zuul-build-dashboard-software-factory<\/span><span class=\"w\">\n      <\/span><span class=\"c1\">#  - zuul-build-dashboard-opendev<\/span><span class=\"w\">\n      <\/span><span class=\"c1\">#  - nodejs-run-lint:<\/span><span class=\"w\">\n      <\/span><span class=\"c1\">#      vars:<\/span><span class=\"w\">\n      <\/span><span class=\"c1\">#        zuul_work_dir: &quot;{{ zuul.project.src_dir }}\/web&quot;<\/span><span class=\"w\">\n      <\/span><span class=\"c1\">#  - nodejs-run-test:<\/span><span class=\"w\">\n      <\/span><span class=\"c1\">#      vars:<\/span><span class=\"w\">\n      <\/span><span class=\"c1\">#        zuul_work_dir: &quot;{{ zuul.project.src_dir }}\/web&quot;<\/span><span class=\"w\">\n      <\/span><span class=\"c1\">#      files:<\/span><span class=\"w\">\n      <\/span><span class=\"c1\">#        - web\/.*<\/span><span class=\"w\">\n      <\/span><span class=\"c1\">#  - zuul-stream-functional-2.8<\/span><span class=\"w\">\n      <\/span><span class=\"c1\">#  - zuul-stream-functional-2.9<\/span><span class=\"w\">\n      <\/span><span class=\"c1\">#  - zuul-stream-functional-5<\/span><span class=\"w\">\n      <\/span><span class=\"c1\">#  - zuul-tox-remote<\/span><span class=\"w\">\n      <\/span><span class=\"c1\">#  - zuul-quick-start:<\/span><span class=\"w\">\n      <\/span><span class=\"c1\">#      requires: nodepool-container-image<\/span><span class=\"w\">\n      <\/span><span class=\"c1\">#      dependencies: zuul-build-image<\/span><span class=\"w\">\n      <\/span><span class=\"c1\">#  - zuul-tox-zuul-client<\/span><span class=\"w\">\n      <\/span><span class=\"c1\">#  - zuul-build-python-release<\/span>\n<\/pre>\n<p>Don't forget however to uncomment these when your patch is ready for review; otherwise\nit has no chance to get merged. :)<\/p>\n<p>It would even be possible to limit the tox-py* job to run a given set of tests rather than the\nfull unit test suite, but I strongly recommend against doing that. This would risk hiding some\nunexpected side effects.<\/p>\n<\/div>\n<div class=\"section\" id=\"the-example-compose\">\n<h2>The example compose<\/h2>\n<p><a class=\"reference external\" href=\"https:\/\/zuul-ci.org\/docs\/zuul\/latest\/tutorials\/quick-start.html\">Zuul's documentation<\/a> provides\na very nifty Docker (or podman) <a class=\"reference external\" href=\"https:\/\/opendev.org\/zuul\/zuul\/src\/branch\/master\/doc\/source\/examples\/docker-compose.yaml\">compose file<\/a>.\nwith just:<\/p>\n<pre class=\"code bash literal-block\">\n<span class=\"nb\">cd<\/span><span class=\"w\"> <\/span>doc\/source\/examples<span class=\"w\"> <\/span><span class=\"o\">&amp;&amp;<\/span><span class=\"w\"> <\/span>podman-compose<span class=\"w\"> <\/span>up\n<\/pre>\n<p>You end up with a gerrit service, and a fully operational Zuul with a tenant and a few projects\npre-configured, in just minutes. This makes it super simple to run some basic workflows on this setup. It is\neven possible to start a <a class=\"reference external\" href=\"https:\/\/zuul-ci.org\/docs\/zuul\/latest\/tutorials\/keycloak.html\">Keycloak server to add authentication<\/a>!<\/p>\n<p>But what I appreciate most is the ability to live-patch Zuul if you want to test some code immediately.\nSince I don't want to modify the upstream compose file, I just &quot;brutishly&quot; copy the python code into the\nservice containers and restart them. Let's say I have modified the REST API (assuming I am still in the\ndoc\/source\/examples directory) - replace <cite>podman<\/cite> with <cite>docker<\/cite> depending on what you use:<\/p>\n<pre class=\"code bash literal-block\">\npodman<span class=\"w\"> <\/span>cp<span class=\"w\"> <\/span>..\/..\/..\/zuul<span class=\"w\"> <\/span>examples_web_1:\/usr\/local\/lib\/python3.8\/site-packages\/<span class=\"w\">\n<\/span>podman-compose<span class=\"w\"> <\/span>restart<span class=\"w\"> <\/span>web\n<\/pre>\n<p>And that's it! The web component is now running your modified code. You can check the service's logs\nwith:<\/p>\n<pre class=\"code bash literal-block\">\npodman<span class=\"w\"> <\/span>logs<span class=\"w\"> <\/span>-f<span class=\"w\"> <\/span>examples_web_1\n<\/pre>\n<p>When you are done playing, make sure to destroy your patched containers with <cite>podman-compose down<\/cite>\nso that you start from a clean slate next time you deploy the compose.<\/p>\n<\/div>\n<div class=\"section\" id=\"gui-development\">\n<h2>GUI development<\/h2>\n<p>Setting up a development server is pretty easy by following <a class=\"reference external\" href=\"https:\/\/zuul-ci.org\/docs\/zuul\/latest\/developer\/javascript.html#development\">the upstream documentation.<\/a>\nEspecially useful is the ability to run the GUI against a Zuul REST server of my choosing;\nif I want to use the web service from the example compose I would run:<\/p>\n<pre class=\"code bash literal-block\">\n<span class=\"nv\">REACT_APP_ZUUL_API<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;http:\/\/localhost:9000\/api\/&quot;<\/span><span class=\"w\"> <\/span>yarn<span class=\"w\"> <\/span>start\n<\/pre>\n<p>It is also totally possible to use softwarefactory-project.io's or Opendev's Zuul instance\nthis way; however you are likely to run into <a class=\"reference external\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/CORS\">CORS-related problems<\/a> in your browser since\nthe &quot;origin&quot; header differs from the value allowed by the distant servers. This a security\nmeasure to ensure malicious javascript code living on a third-party server cannot be accidentally\nallowed to do nasty stuff, thus CORS shouldn't be disabled (and as far as I can tell, most browsers\nwill make it very hard to do so in order to discourage you).<\/p>\n<p>You can circumvent this problem by using a CORS proxy. I have been using this one without any problem\nso far whenever I want to see how my changes look with data from Opendev's Zuul:<\/p>\n<pre class=\"code bash literal-block\">\npodman<span class=\"w\"> <\/span>run<span class=\"w\"> <\/span>-p<span class=\"w\"> <\/span><span class=\"m\">8000<\/span>:8000<span class=\"w\"> <\/span>bulletmark\/corsproxy<span class=\"w\"> <\/span><span class=\"m\">8000<\/span>:zuul.opendev.org\n<\/pre>\n<p>Then launch the dev server:<\/p>\n<pre class=\"code bash literal-block\">\n<span class=\"nv\">REACT_APP_ZUUL_API<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;http:\/\/localhost:8000\/api\/&quot;<\/span><span class=\"w\"> <\/span>yarn<span class=\"w\"> <\/span>start\n<\/pre>\n<\/div>\n<div class=\"section\" id=\"conclusion\">\n<h2>Conclusion<\/h2>\n<p>This article presented a few ways to shorten the feedback loop when contributing to Zuul. It\nis by no means exhaustive and I am sure there are other great ways to set up a dev environment\nfor the project. I'd love to hear about your own practices!<\/p>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2022 Jun 03 to Jun 22 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2022-jun-03-to-jun-22-summary.html","rel":"alternate"}},"published":"2022-06-22T10:00:00+00:00","updated":"2022-06-22T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2022-06-22:\/sprint-2022-jun-03-to-jun-22-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>zuul-client 0.1.0 was released.<\/li>\n<li>We added few patches into the logscraper and logsender tool that we found during applying new ci log workflow into the sf-config project (empty zuul job list infinity loop and move arg \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>zuul-client 0.1.0 was released.<\/li>\n<li>We added few patches into the logscraper and logsender tool that we found during applying new ci log workflow into the sf-config project (empty zuul job list infinity loop and move arg parser params into the config file)<\/li>\n<li>We create a docker account for opendev ci log processing project<\/li>\n<li>We added a CI job that is creating and pushing the container images into the docker registry<\/li>\n<li>We added a feature for logscraper to get some statistics that later will be available in Prometheus about job count<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul>\n<li><p class=\"first\">We have released tags for our custom keycloak extensions after validating they work with keycloak 15:<\/p>\n<blockquote>\n<ul class=\"simple\">\n<li>github-ssh-mapper<\/li>\n<li>mqtt-event-listener<\/li>\n<\/ul>\n<\/blockquote>\n<\/li>\n<li><p class=\"first\">We worked on reactivating these extensions with containerized keycloak.<\/p>\n<\/li>\n<li><p class=\"first\">We worked on moving log gearman client and worker to use logscraper and logsender (ci log processing log workflow) and it will also remove logstash service<\/p>\n<\/li>\n<li><p class=\"first\">We are investigating how to deploy software factory on OpenShift using kubebuilder.<\/p>\n<\/li>\n<li><p class=\"first\">We Containarized Mysql<\/p>\n<\/li>\n<li><p class=\"first\">We've started to containarized SF Gateway<\/p>\n<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Running Software Factory on OpenShift","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/running-software-factory-on-openshift.html","rel":"alternate"}},"published":"2022-06-08T00:00:00+00:00","updated":"2022-06-08T00:00:00+00:00","author":{"name":"tristanC"},"id":"tag:www.softwarefactory-project.io,2022-06-08:\/running-software-factory-on-openshift.html","summary":"<p>This article presents a plan for running Software Factory on OpenShift.<\/p>\n<div class=\"section\" id=\"context-and-problem-statement\">\n<h2>Context and Problem Statement<\/h2>\n<p>We are looking for solutions to the following list of pain points with the current system:<\/p>\n<ul class=\"simple\">\n<li>Where are the services secrets, and how can I rotate them? This is a key requirement for auditing purpose \u2026<\/li><\/ul><\/div>","content":"<p>This article presents a plan for running Software Factory on OpenShift.<\/p>\n<div class=\"section\" id=\"context-and-problem-statement\">\n<h2>Context and Problem Statement<\/h2>\n<p>We are looking for solutions to the following list of pain points with the current system:<\/p>\n<ul class=\"simple\">\n<li>Where are the services secrets, and how can I rotate them? This is a key requirement for auditing purpose.<\/li>\n<li>The plateform is overloaded, what is the process to increase the number of executors?<\/li>\n<li>Zuul is not voting on $change_url, what is going on? (the service logs are presently not centralized)<\/li>\n<li>What base system should I use, CentOS? Fedora? Debian?<\/li>\n<li>How can I run a pre-production deployment with a local checkout of the Zuul source code?<\/li>\n<li>sfconfig execution is slow, for example sf-project.io deployment takes more than one hour to converge.<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"considered-options\">\n<h2>Considered Options<\/h2>\n<ul class=\"simple\">\n<li>[option 1] Modularize the Ansible roles<\/li>\n<li>[option 2] Migrate to Kubernetes<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"decision-outcome\">\n<h2>Decision Outcome<\/h2>\n<p>We would like to investigate if and how Kubernetes can be a solution.\nHere are the condition of satisfaction for the initial architecture:<\/p>\n<div class=\"section\" id=\"localhost-configuration\">\n<h3>Localhost configuration<\/h3>\n<p>When the user does not have a KUBECONFIG ready, sfconfig setup a single node cluster with <a class=\"reference external\" href=\"https:\/\/kind.sigs.k8s.io\/\">Kind<\/a>.\nThen the system requires a working <cite>kubectl<\/cite> command.<\/p>\n<\/div>\n<div class=\"section\" id=\"service-deployment\">\n<h3>Service deployment<\/h3>\n<p>Using common manifests, the user can deploy custom architecture, for example <em>kubectl apply -f zuul+ci-log-processor.yaml<\/em>:<\/p>\n<ul class=\"simple\">\n<li>Deploys: opensearch, zuul and ci log processor.<\/li>\n<li>Provides: zuul-web url and kibana url.<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"developper-mode\">\n<h3>Developper mode<\/h3>\n<p>Running <em>sfconfig develop ~\/src\/opendev.org\/openstack\/ci-log-processing<\/em> restart the ci-log-processor service using a local copy of the source.\nSee for example this guide:  <a class=\"reference external\" href=\"https:\/\/github.com\/kubernetes-sigs\/kind\/issues\/2430\">https:\/\/github.com\/kubernetes-sigs\/kind\/issues\/2430<\/a><\/p>\n<\/div>\n<div class=\"section\" id=\"migration-from-sfconfig-yaml-and-arch-yaml\">\n<h3>Migration from sfconfig.yaml and arch.yaml<\/h3>\n<p>There is a procedure to migrate the legacy system.<\/p>\n<\/div>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2022 May 13 to Jun 01 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2022-may-13-to-jun-01-summary.html","rel":"alternate"}},"published":"2022-06-01T10:00:00+00:00","updated":"2022-06-01T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2022-06-01:\/sprint-2022-may-13-to-jun-01-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We proposed a patch for log sender to skip &quot;DEBUG&quot; messages due Opensearch is out of space<\/li>\n<li>We proposed to add performance.json file into separate index<\/li>\n<li>We are helping TripleO team to create functional tests and Ansible \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We proposed a patch for log sender to skip &quot;DEBUG&quot; messages due Opensearch is out of space<\/li>\n<li>We proposed to add performance.json file into separate index<\/li>\n<li>We are helping TripleO team to create functional tests and Ansible playbooks that will deploy Elastic Recheck tool<\/li>\n<li>We proposed patch for Zuul to include python passlib library into the default Zuul environment<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We developed the zuul-weeder service: <a class=\"reference external\" href=\"https:\/\/www.softwarefactory-project.io\/introducing-zuul-weeder.html\">https:\/\/www.softwarefactory-project.io\/introducing-zuul-weeder.html<\/a><\/li>\n<li>We discussed the design of the version 4 about how the Ansible roles should be defined.<\/li>\n<li>We finalized the 3.7 release<\/li>\n<li>We Containarized Zookeeper and Grafana<\/li>\n<li>We've started to containarized MySQL<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"K1S is not working properly after upgrade","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/k1s-is-not-working-properly-after-upgrade.html","rel":"alternate"}},"published":"2022-05-25T11:00:00+00:00","updated":"2022-05-25T11:00:00+00:00","author":{"name":"sf"},"id":"tag:www.softwarefactory-project.io,2022-05-25:\/k1s-is-not-working-properly-after-upgrade.html","summary":"<p>The k1s hypervisor might not work properly after update Centos instance and\nafter upgrading Software Factory Project to the new release recently.<\/p>\n<p>If the hypervisor is not working as expected, we suggest to downgrade the\npodman package from package <cite>podman-1.6.4-32<\/cite> to <cite>podman-1.6.4-29<\/cite>.<\/p>\n<p>To verify if your \u2026<\/p>","content":"<p>The k1s hypervisor might not work properly after update Centos instance and\nafter upgrading Software Factory Project to the new release recently.<\/p>\n<p>If the hypervisor is not working as expected, we suggest to downgrade the\npodman package from package <cite>podman-1.6.4-32<\/cite> to <cite>podman-1.6.4-29<\/cite>.<\/p>\n<p>To verify if your deployment is affected by the issue you can run the following command:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"nb\">echo<\/span><span class=\"w\"> <\/span><span class=\"nb\">test<\/span><span class=\"w\"> <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span>podman<span class=\"w\"> <\/span><span class=\"nb\">exec<\/span><span class=\"w\"> <\/span>-i<span class=\"w\"> <\/span>interactive-test<span class=\"w\"> <\/span>cat\n<\/pre><\/div>\n<p>To install previous podman package version, you can execute command on k1s host:<\/p>\n<div class=\"highlight\"><pre><span><\/span>yum<span class=\"w\"> <\/span>downgrade<span class=\"w\"> <\/span>podman-1.6.4-29.el7_9.x86_64\n<\/pre><\/div>\n<p>The Software Factory team has reported that issue to the podman community, but\nthe patch is not released yet. In the future, when the bug is closed, we will\ncreate new release that is removing temporary workaround.<\/p>\n<p>You can find more information about the issue at <a class=\"reference external\" href=\"https:\/\/bugzilla.redhat.com\/show_bug.cgi?id=2087994\">https:\/\/bugzilla.redhat.com\/show_bug.cgi?id=2087994<\/a><\/p>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Introducing Zuul Weeder","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/introducing-zuul-weeder.html","rel":"alternate"}},"published":"2022-05-23T00:00:00+00:00","updated":"2022-05-23T00:00:00+00:00","author":{"name":"tristanC and fboucher"},"id":"tag:www.softwarefactory-project.io,2022-05-23:\/introducing-zuul-weeder.html","summary":"<p>The last month we have been developping a new tool to help us operate <a class=\"reference external\" href=\"https:\/\/zuul-ci.org\">Zuul<\/a>,\nand today we are happy to announce the first release of the project.<\/p>\n<ul class=\"simple\">\n<li>Demo deployment <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/weeder\">https:\/\/sofwarefactory-project.io\/weeder<\/a>,<\/li>\n<li>Source code <a class=\"reference external\" href=\"https:\/\/github.com\/softwarefactory-project\/zuul-weeder#readme\">software-factory\/zuul-weeder<\/a>.<\/li>\n<\/ul>\n<div class=\"section\" id=\"overview-and-scope\">\n<h2>Overview and scope<\/h2>\n<p>Zuul Weeder analyzes the configuration objects such as jobs \u2026<\/p><\/div>","content":"<p>The last month we have been developping a new tool to help us operate <a class=\"reference external\" href=\"https:\/\/zuul-ci.org\">Zuul<\/a>,\nand today we are happy to announce the first release of the project.<\/p>\n<ul class=\"simple\">\n<li>Demo deployment <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/weeder\">https:\/\/sofwarefactory-project.io\/weeder<\/a>,<\/li>\n<li>Source code <a class=\"reference external\" href=\"https:\/\/github.com\/softwarefactory-project\/zuul-weeder#readme\">software-factory\/zuul-weeder<\/a>.<\/li>\n<\/ul>\n<div class=\"section\" id=\"overview-and-scope\">\n<h2>Overview and scope<\/h2>\n<p>Zuul Weeder analyzes the configuration objects such as jobs and nodesets and provides a search interface for:<\/p>\n<ul class=\"simple\">\n<li>Depencencies: what depends on an object.<\/li>\n<li>Requirements: what is needed by an object.<\/li>\n<li>URL of the configuration files that contains the object.<\/li>\n<\/ul>\n<p>The goal is to help evaluate the impact of a configuration change.\nZuul Weeder leverage a generic dependency graph using the data found in the ZooKeeper database\nto collect every configuration elements used by any tenants.\nFor example, when removing a node label or a repository.<\/p>\n<\/div>\n<div class=\"section\" id=\"usage\">\n<h2>Usage<\/h2>\n<p>The service provide two functions:<\/p>\n<ul class=\"simple\">\n<li><em>\/search\/$name<\/em> returns the list of object matching the requested name.<\/li>\n<li><em>\/object\/$type\/$name<\/em> returns\n- the list of configuration file url that directly defines or uses the object,\n- the list of related objects that are reachable, either by requirement or by dependency.<\/li>\n<\/ul>\n<p>For example, by visiting <em>\/search\/centos<\/em>, the service returns:<\/p>\n<ul class=\"simple\">\n<li>job tripleo-centos<\/li>\n<li>nodeset centos<\/li>\n<li>label cloud-centos<\/li>\n<\/ul>\n<img alt=\"None\" src=\"images\/zuul-weeder-search.png\" \/>\n<div class=\"line-block\">\n<div class=\"line\"><br \/><\/div>\n<\/div>\n<p>And by visiting <em>\/object\/nodeset\/centos<\/em>, the service returns:<\/p>\n<ul class=\"simple\">\n<li>The list of zuul.yaml file url that contains a nodeset named <em>centos<\/em>.<\/li>\n<li>The list of jobs and project that depends on this nodeset.<\/li>\n<li>The list of node label name that is required by this nodeset.<\/li>\n<\/ul>\n<p>The results can be scoped to a specific tenant by using the <em>\/tenant\/$tenant<\/em> url prefix.<\/p>\n<img alt=\"None\" src=\"images\/zuul-weeder-object.png\" \/>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Software Factory 3.7 has been released !","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/software-factory-37-has-been-released.html","rel":"alternate"}},"published":"2022-05-13T12:00:00+00:00","updated":"2022-05-13T12:00:00+00:00","author":{"name":"sf"},"id":"tag:www.softwarefactory-project.io,2022-05-13:\/software-factory-37-has-been-released.html","summary":"<p>We are glad to announce the release of Software Factory 3.7. This release mainly features:<\/p>\n<ul class=\"simple\">\n<li>Gerrit is upgraded to the version 3.4.3<\/li>\n<li>Zuul and Nodepool are upgraded to 5.2.3 and 5.0.0<\/li>\n<li>Gerrit, Zuul, Nodepool, Etherpad, OpenSearch services are now containerized<\/li>\n<\/ul>\n<p>Read the details \u2026<\/p>","content":"<p>We are glad to announce the release of Software Factory 3.7. This release mainly features:<\/p>\n<ul class=\"simple\">\n<li>Gerrit is upgraded to the version 3.4.3<\/li>\n<li>Zuul and Nodepool are upgraded to 5.2.3 and 5.0.0<\/li>\n<li>Gerrit, Zuul, Nodepool, Etherpad, OpenSearch services are now containerized<\/li>\n<\/ul>\n<p>Read the details <a class=\"reference external\" href=\"https:\/\/www.softwarefactory-project.io\/releases\/3.7\/\">here<\/a>.<\/p>\n<p>To update your deployment see the <a class=\"reference external\" href=\"https:\/\/docs.softwarefactory-project.io\/sf-config-3.7\/operator\/upgrade.html#upgrade-software-factory\">update<\/a> section.<\/p>\n<p>If you experience any difficulties, please don't hesitate to raise an issue. You can reach us\non <a class=\"reference external\" href=\"https:\/\/app.element.io\/#\/room\/#softwarefactory-project:matrix.org\">matrix<\/a>.<\/p>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2022 Apr 22 to May 11 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2022-apr-22-to-may-11-summary.html","rel":"alternate"}},"published":"2022-05-11T10:00:00+00:00","updated":"2022-05-11T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2022-05-11:\/sprint-2022-apr-22-to-may-11-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We investigated with issue related to the disk flooding. So far, we create a workaround for wait some time before push next logs<\/li>\n<li>We proposed an alternative patch that will include performance.json fields in logsender that all \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We investigated with issue related to the disk flooding. So far, we create a workaround for wait some time before push next logs<\/li>\n<li>We proposed an alternative patch that will include performance.json fields in logsender that all doc will be in same index<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We added upgrade tasks for sf-container role<\/li>\n<li>We updated our infra to SF 3.7 as part of SF release process<\/li>\n<li>we added zuul-client to sf master and 3.7 to cover bugs in zuul CLI introduced in 5.X<\/li>\n<li>We were able to make Keycloak work as SSO with SF<\/li>\n<li>We change all internal variables from Elasticsearch to Opensearch<\/li>\n<li>zuul-weeder - We implemented Graph ingest for jobs and nodesets and a demo CLI<\/li>\n<li>zuul-weeder - We implemented tenant and connection reading to create ability to filter config element by tenants<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2022 Apr 01 to Apr 20 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2022-apr-01-to-apr-20-summary.html","rel":"alternate"}},"published":"2022-04-20T10:00:00+00:00","updated":"2022-04-20T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2022-04-20:\/sprint-2022-apr-01-to-apr-20-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We reviewed the ibm cloud storage log upload role: Zuul can now store logs in IBM cloud.<\/li>\n<li>We updated the gerritbot-matrix service to indicate the wipness of a change.<\/li>\n<li>We provide feature that logsender is parsing performance.json \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We reviewed the ibm cloud storage log upload role: Zuul can now store logs in IBM cloud.<\/li>\n<li>We updated the gerritbot-matrix service to indicate the wipness of a change.<\/li>\n<li>We provide feature that logsender is parsing performance.json file that is cummulating job utilization (<a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/openstack\/devstack\/+\/837139\">https:\/\/review.opendev.org\/c\/openstack\/devstack\/+\/837139<\/a>)<\/li>\n<li>We added more information for Openstack doc about Opensearch<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We added dumb-init for zuul and nodepool containers to avoid to have defunct process<\/li>\n<li>We added a backup\/restore process for zuul keys for they are now stored in zookeeper<\/li>\n<li>We added \/bin\/{zuul,nodepool} on hosts where zuul-scheduler, nodepool-{builder-launcher} containers are used for alias don't work for any usecases<\/li>\n<li>We clarified update\/upgrade naming on sfconfig roles by renaming tasks\/update.yml to tasks\/config_update.yml for all roles<\/li>\n<li>We continue to backport fixes for SF 3.7 release<\/li>\n<li>We evaluated algebraic-graph to build a POC of a zuul-weeder project <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/plugins\/gitiles\/software-factory\/mob-programing\/+\/f7c5e32f0ff34cac311d276c1a5c7b5e8ce7d268\/zuul-weeder\/solve.hs\">https:\/\/softwarefactory-project.io\/r\/plugins\/gitiles\/software-factory\/mob-programing\/+\/f7c5e32f0ff34cac311d276c1a5c7b5e8ce7d268\/zuul-weeder\/solve.hs<\/a><\/li>\n<li>We started to work on zuul-weeder implementation <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/plugins\/gitiles\/software-factory\/zuul-weeder\/+\/refs\/heads\/master\">https:\/\/softwarefactory-project.io\/r\/plugins\/gitiles\/software-factory\/zuul-weeder\/+\/refs\/heads\/master<\/a><\/li>\n<li>We are working on the integration of Keycloak with Opensearch and Opensearch-Dashboards<\/li>\n<li>We integrated the latest Zuul 5.2 by improving the zuul restart process to accomodate the new ZooKeeper usage.<\/li>\n<li>We worked on managing the container upgrade.<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2022 Mar 11 to Mar 30 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2022-mar-11-to-mar-30-summary.html","rel":"alternate"}},"published":"2022-03-30T10:00:00+00:00","updated":"2022-03-30T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2022-03-30:\/sprint-2022-mar-11-to-mar-30-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We improved the ci logscrapper by correctly handling build history and efficiently processing timestamps.<\/li>\n<li>We reviewed the unrestricted-ansible specification.<\/li>\n<li>We proposed CVE fix for zuul-web dependencies. More coming <a class=\"reference external\" href=\"https:\/\/bugzilla.redhat.com\/buglist.cgi?component=zuul&amp;product=Fedora\">https:\/\/bugzilla.redhat.com\/buglist.cgi?component=zuul&amp;product=Fedora \u2026<\/a><\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We improved the ci logscrapper by correctly handling build history and efficiently processing timestamps.<\/li>\n<li>We reviewed the unrestricted-ansible specification.<\/li>\n<li>We proposed CVE fix for zuul-web dependencies. More coming <a class=\"reference external\" href=\"https:\/\/bugzilla.redhat.com\/buglist.cgi?component=zuul&amp;product=Fedora\">https:\/\/bugzilla.redhat.com\/buglist.cgi?component=zuul&amp;product=Fedora<\/a><\/li>\n<li>We configure logsender tool with Opensearch Opendev<\/li>\n<li>We added more information into documentation to Openstack ci-log-processing project; many code improvements related to logscraper tool and logsender<\/li>\n<li>We added more log to be pushed to the Opensearch<\/li>\n<li>We added basic visualization to Opensearch openstack <a class=\"reference external\" href=\"https:\/\/opensearch.logs.openstack.org\/_dashboards\/app\/discover?security_tenant=global\">https:\/\/opensearch.logs.openstack.org\/_dashboards\/app\/discover?security_tenant=global<\/a> : user: openstack password: openstack<\/li>\n<li>we proposed a spec for ACL handling with respect to external sources (gerrit, github etc) <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/777629\">https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/777629<\/a><\/li>\n<li>we proposed zuul-client dequeue-all <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul-client\/+\/835319\">https:\/\/review.opendev.org\/c\/zuul\/zuul-client\/+\/835319<\/a><\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul>\n<li><p class=\"first\">We patched Zuul <a class=\"reference external\" href=\"https:\/\/www.softwarefactory-project.io\/zuul-security-fix-ansible-plugin-loading.html\">https:\/\/www.softwarefactory-project.io\/zuul-security-fix-ansible-plugin-loading.html<\/a><\/p>\n<\/li>\n<li><p class=\"first\">We are working on the release process of sf-3.7:<\/p>\n<blockquote>\n<ul class=\"simple\">\n<li>We created sf-3.7 targets on koji and we've populated a sf-3.7-release tag<\/li>\n<li>We validated sf upgrade from 3.6 to 3.7 on 3 test instances with production data.<\/li>\n<li>We updated zuul containers to version 5.2.0 on sf-master<\/li>\n<li>The next steps are updating our production environments to validate it works as expected, then finalize the release sf 3.7.<\/li>\n<\/ul>\n<\/blockquote>\n<\/li>\n<li><p class=\"first\">We added support for free-form zuul ACLs in the configuration engine<\/p>\n<\/li>\n<li><p class=\"first\">We updated Keycloak container<\/p>\n<\/li>\n<li><p class=\"first\">We are working on Keycloak Intergration with Gerrit and Zuul<\/p>\n<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Completing the first release of logreduce-rust","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/completing-the-first-release-of-logreduce-rust.html","rel":"alternate"}},"published":"2022-03-29T00:00:00+00:00","updated":"2022-03-29T00:00:00+00:00","author":{"name":"tristanC"},"id":"tag:www.softwarefactory-project.io,2022-03-29:\/completing-the-first-release-of-logreduce-rust.html","summary":"<p>I am happy to announce that the <a class=\"reference external\" href=\"https:\/\/github.com\/logreduce\/logreduce-rust\">logreduce-rust<\/a> project now implements a minimum viable product.\nIt can be used to compare two remote directories like this: <cite>logreduce diff build-log-url1 build-log-url2<\/cite>.\nThis article introduces the latest features.<\/p>\n<p>In this post I will write about:<\/p>\n<ul class=\"simple\">\n<li>My choices regarding user input processing.<\/li>\n<li>Data \u2026<\/li><\/ul>","content":"<p>I am happy to announce that the <a class=\"reference external\" href=\"https:\/\/github.com\/logreduce\/logreduce-rust\">logreduce-rust<\/a> project now implements a minimum viable product.\nIt can be used to compare two remote directories like this: <cite>logreduce diff build-log-url1 build-log-url2<\/cite>.\nThis article introduces the latest features.<\/p>\n<p>In this post I will write about:<\/p>\n<ul class=\"simple\">\n<li>My choices regarding user input processing.<\/li>\n<li>Data type models using static dispatch.<\/li>\n<li>Threadpool based concurrency.<\/li>\n<li>What my thoughts are on Rust in retrospect.<\/li>\n<\/ul>\n<div class=\"admonition note\">\n<p class=\"first admonition-title\">Note<\/p>\n<p>This article is part of a blog post series about the latest logreduce improvements\nusing the Rust programing language. Please see the series' earlier articles:<\/p>\n<ul class=\"last simple\">\n<li>Part1: <a class=\"reference external\" href=\"https:\/\/www.softwarefactory-project.io\/improving-logreduce-with-rust.html\">Improving logreduce tokenizer<\/a>.<\/li>\n<li>Part2: <a class=\"reference external\" href=\"https:\/\/www.softwarefactory-project.io\/implementing-logreduce-nearest-neighbors-model-in-rust.html\">Implementing logreduce nearest neighbors model<\/a>.<\/li>\n<li>Part3: <a class=\"reference external\" href=\"https:\/\/www.softwarefactory-project.io\/introducing-the-byteslines-iterator.html\">Introducing the BytesLines iterator<\/a>.<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"implementing-the-model-processor\">\n<h2>Implementing the model processor<\/h2>\n<p>The model processor is the main part that was missing from the new code base.\nThe goal is to provide a flexible API that combines the log line iterator, the tokenizer, and the nearest neighbors model.\nThe API requirements are:<\/p>\n<ul class=\"simple\">\n<li>The processing needs to work in chunk to leverage efficient vectorized operations.<\/li>\n<li>The context lines surrounding an anomaly need to be collected so that the output can be analyzed offline, without access to the content.<\/li>\n<li>Duplicated lines should be removed.<\/li>\n<\/ul>\n<p>Addressing these requirements involves a complex algorithm with a few edge cases, for example, to keep track of the lines preceeding an anomaly.\nUsing the beloved Iterator interface I was able to implement a simple abstraction.\nThe resulting API can be used by simply providing an Index and a Read value:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"w\"> <\/span><span class=\"k\">pub<\/span><span class=\"w\"> <\/span><span class=\"k\">struct<\/span><span class=\"w\"> <\/span><span class=\"nc\">ChunkProcessor<\/span><span class=\"o\">&lt;&#39;<\/span><span class=\"na\">index<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">R<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nc\">Read<\/span><span class=\"o\">&gt;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">     <\/span><span class=\"n\">reader<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nc\">logreduce_iterator<\/span><span class=\"p\">::<\/span><span class=\"n\">BytesLines<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">R<\/span><span class=\"o\">&gt;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">     <\/span><span class=\"n\">index<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"kp\">&amp;<\/span><span class=\"o\">&#39;<\/span><span class=\"na\">index<\/span><span class=\"w\"> <\/span><span class=\"nc\">ChunkIndex<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">     <\/span><span class=\"c1\">\/\/ The raw log line with their global position<\/span>\n<span class=\"w\">     <\/span><span class=\"n\">buffer<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nb\">Vec<\/span><span class=\"o\">&lt;<\/span><span class=\"p\">(<\/span><span class=\"n\">logreduce_iterator<\/span><span class=\"p\">::<\/span><span class=\"n\">LogLine<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"kt\">usize<\/span><span class=\"p\">)<\/span><span class=\"o\">&gt;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">     <\/span><span class=\"o\">..<\/span><span class=\"p\">.<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"k\">impl<\/span><span class=\"o\">&lt;&#39;<\/span><span class=\"na\">index<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">R<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nc\">Read<\/span><span class=\"o\">&gt;<\/span><span class=\"w\"> <\/span><span class=\"nb\">Iterator<\/span><span class=\"w\"> <\/span><span class=\"k\">for<\/span><span class=\"w\"> <\/span><span class=\"n\">ChunkProcessor<\/span><span class=\"o\">&lt;&#39;<\/span><span class=\"na\">index<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">R<\/span><span class=\"o\">&gt;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">type<\/span><span class=\"w\"> <\/span><span class=\"nc\">Item<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"nb\">Result<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">AnomalyContext<\/span><span class=\"o\">&gt;<\/span><span class=\"p\">;<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<p>You can check the full implementation in the <a class=\"reference external\" href=\"https:\/\/github.com\/logreduce\/logreduce-rust\/blob\/main\/model\/src\/process.rs\">process.rs<\/a> module.<\/p>\n<\/div>\n<div class=\"section\" id=\"using-static-dispatch-for-the-data-model\">\n<h2>Using static dispatch for the data model<\/h2>\n<p>With all the core modules in place, I needed to define a data model to implement the frontend logic.\nLogreduce can work with a variety of content, which can be accessed through different source types:<\/p>\n<ul class=\"simple\">\n<li>Local path.<\/li>\n<li>Remote url.<\/li>\n<li>Journald socket.<\/li>\n<\/ul>\n<p>I initially created a ContentSource trait to define how to get the sources of a given Content:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"w\"> <\/span><span class=\"k\">trait<\/span><span class=\"w\"> <\/span><span class=\"n\">ContentSource<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">   <\/span><span class=\"k\">fn<\/span><span class=\"w\"> <\/span><span class=\"nf\">get_sources<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"bp\">self<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"nb\">Vec<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">Source<\/span><span class=\"o\">&gt;<\/span><span class=\"p\">;<\/span>\n<span class=\"w\"> <\/span><span class=\"p\">}<\/span>\n\n<span class=\"k\">impl<\/span><span class=\"w\"> <\/span><span class=\"n\">ContentSource<\/span><span class=\"w\"> <\/span><span class=\"k\">for<\/span><span class=\"w\"> <\/span><span class=\"n\">File<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">  <\/span><span class=\"k\">fn<\/span><span class=\"w\"> <\/span><span class=\"nf\">get_sources<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"bp\">self<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"nb\">Vec<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">Source<\/span><span class=\"o\">&gt;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"bp\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">path<\/span><span class=\"p\">().<\/span><span class=\"n\">ends_with<\/span><span class=\"p\">(<\/span><span class=\"sc\">&#39;\/&#39;<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">       <\/span><span class=\"c1\">\/\/ read dir<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span><span class=\"w\"> <\/span><span class=\"k\">else<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">       <\/span><span class=\"c1\">\/\/ a single file<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">  <\/span><span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"k\">impl<\/span><span class=\"w\"> <\/span><span class=\"n\">ContentSource<\/span><span class=\"w\"> <\/span><span class=\"k\">for<\/span><span class=\"w\"> <\/span><span class=\"n\">Build<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">  <\/span><span class=\"k\">fn<\/span><span class=\"w\"> <\/span><span class=\"nf\">get_sources<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"bp\">self<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"nb\">Vec<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">Source<\/span><span class=\"o\">&gt;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ list remote sources<\/span>\n<span class=\"w\">  <\/span><span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<p>However this adds a bit of complexity to the report implementation.\nFor example, the state contains the list of baselines sources:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"k\">struct<\/span><span class=\"w\"> <\/span><span class=\"nc\">Report<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">  <\/span><span class=\"n\">baselines<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nb\">Vec<\/span><span class=\"o\">&lt;<\/span><span class=\"k\">dyn<\/span><span class=\"w\"> <\/span><span class=\"n\">ContentSource<\/span><span class=\"o\">&gt;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">  <\/span><span class=\"o\">..<\/span><span class=\"p\">.<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<p>\u2026 but this does not work because of this error:<\/p>\n<div class=\"highlight\"><pre><span><\/span>error[E0277]: the size for values of type `(dyn ContentSource + &#39;static)` cannot be known at compilation time\n   --&gt; model\/src\/model.rs:25:16\n    |\n25  |     baselines: Vec&lt;dyn ContentSource&gt;,\n    |                ^^^^^^^^^^^^^^^^^^^^^^ doesn&#39;t have a size known at compile-time\n    |\n    = help: the trait `Sized` is not implemented for `(dyn ContentSource + &#39;static)`\nnote: required by a bound in `Vec`\n<\/pre><\/div>\n<p>This makes sense because any type can implement ContentSource, and the compiler needs to know how much\nmemory they need. Thus we can use a Box to fix that, which is how most languages solve this problem:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"k\">struct<\/span><span class=\"w\"> <\/span><span class=\"nc\">Report<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">baselines<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nb\">Vec<\/span><span class=\"o\">&lt;<\/span><span class=\"nb\">Box<\/span><span class=\"o\">&lt;<\/span><span class=\"k\">dyn<\/span><span class=\"w\"> <\/span><span class=\"n\">ContentSource<\/span><span class=\"o\">&gt;&gt;<\/span><span class=\"p\">,<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<p>Alternatively, we can use a technique called static dispatch with an enum's pattern match:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"k\">enum<\/span><span class=\"w\"> <\/span><span class=\"nc\">Content<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">  <\/span><span class=\"n\">File<\/span><span class=\"p\">(<\/span><span class=\"n\">Path<\/span><span class=\"p\">),<\/span>\n<span class=\"w\">  <\/span><span class=\"n\">CI<\/span><span class=\"p\">(<\/span><span class=\"n\">BuildInfo<\/span><span class=\"p\">),<\/span>\n<span class=\"w\">  <\/span><span class=\"o\">..<\/span><span class=\"p\">.<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"k\">impl<\/span><span class=\"w\"> <\/span><span class=\"n\">Content<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">  <\/span><span class=\"k\">fn<\/span><span class=\"w\"> <\/span><span class=\"nf\">get_sources<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"bp\">self<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"nb\">Vec<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">Source<\/span><span class=\"o\">&gt;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">match<\/span><span class=\"w\"> <\/span><span class=\"bp\">self<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">      <\/span><span class=\"n\">File<\/span><span class=\"p\">(<\/span><span class=\"n\">fp<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"o\">=&gt;<\/span><span class=\"w\"> <\/span><span class=\"n\">files<\/span><span class=\"p\">::<\/span><span class=\"n\">get_sources<\/span><span class=\"p\">(<\/span><span class=\"n\">fp<\/span><span class=\"p\">),<\/span>\n<span class=\"w\">      <\/span><span class=\"n\">CI<\/span><span class=\"p\">(<\/span><span class=\"n\">build<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"o\">=&gt;<\/span><span class=\"w\"> <\/span><span class=\"n\">ci<\/span><span class=\"p\">::<\/span><span class=\"n\">get_sources<\/span><span class=\"p\">(<\/span><span class=\"n\">build<\/span><span class=\"p\">),<\/span>\n<span class=\"w\">      <\/span><span class=\"o\">..<\/span><span class=\"p\">.<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">  <\/span><span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"k\">struct<\/span><span class=\"w\"> <\/span><span class=\"nc\">Report<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">  <\/span><span class=\"n\">baselines<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nb\">Vec<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">Content<\/span><span class=\"o\">&gt;<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<p>The content data type is currently defined using static dispatch, which is simpler for the project.\nHowever this means that new types can't be added dynamically.<\/p>\n<p>I documented the complete model in an <a class=\"reference external\" href=\"https:\/\/adr.github.io\/\">Architectural Decision Record<\/a> you can find here: <a class=\"reference external\" href=\"https:\/\/github.com\/logreduce\/logreduce-rust\/blob\/main\/doc\/adr\/0001-architecture-cli.md\">0001-architecture-cli.md<\/a>.\nYou can check the implementation in the <a class=\"reference external\" href=\"https:\/\/github.com\/logreduce\/logreduce-rust\/blob\/main\/model\/src\/model.rs\">model.rs<\/a> module.<\/p>\n<\/div>\n<div class=\"section\" id=\"crawling-logs-in-parallel-using-a-threadpool\">\n<h2>Crawling logs in parallel using a threadpool<\/h2>\n<p>Another interesting feature of logreduce is that it can seamlessly process remote directories.\nThe goal is to be able to handle a build log url, served as <em>Index Of<\/em> pages, as if it was a local directory.\nThus, I looked into collecting the log files concurrently so that the tree could be traversed quickly.<\/p>\n<p>I initially created an AsyncIterator using the <a class=\"reference external\" href=\"https:\/\/tokio.rs\">tokio.rs<\/a> library.\nTo limit the amount of workers, I used the <a class=\"reference external\" href=\"https:\/\/docs.rs\/futures\/latest\/futures\/stream\/futures_unordered\/struct.FuturesUnordered.html\">FuturesUnordered<\/a> structure as explained in this <a class=\"reference external\" href=\"https:\/\/users.rust-lang.org\/t\/batch-execution-of-futures-in-the-tokio-runtime-or-max-number-of-active-futures-at-a-time\/47659\/4\">max number of active futures at a time<\/a> discussion.\nThat seemed to work great, but implementing the <em>handle response<\/em> part was a bit complicated.\nSome footguns need to be avoided according to this <a class=\"reference external\" href=\"https:\/\/github.com\/rust-lang\/futures-rs\/issues\/2387\">issue<\/a>.\nTo learn more about async Rust, check its working group <a class=\"reference external\" href=\"https:\/\/rust-lang.github.io\/wg-async\/\">wg-async<\/a> page.<\/p>\n<p>From my understanding, Tokio is great for long running tasks like building a server.\nBut for short tasks, such as crawling an http directory, I find it easier to use a threadpool with <a class=\"reference external\" href=\"https:\/\/doc.rust-lang.org\/std\/sync\/mpsc\/\">mpsc<\/a>, a Multi Producer, Single Consumer FIFO queue.\nThus, here is the main function of the logreduce's httpdir library:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"k\">fn<\/span><span class=\"w\"> <\/span><span class=\"nf\">process<\/span><span class=\"p\">(<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">visitor<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"kp\">&amp;<\/span><span class=\"nc\">Visitor<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">client<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"kp\">&amp;<\/span><span class=\"nc\">Client<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">pool<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"kp\">&amp;<\/span><span class=\"nc\">ThreadPool<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">tx<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"kp\">&amp;<\/span><span class=\"nc\">Sender<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">Message<\/span><span class=\"o\">&gt;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">url<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nc\">Url<\/span><span class=\"p\">,<\/span>\n<span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"nb\">Some<\/span><span class=\"p\">(<\/span><span class=\"n\">visitor<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">visitor<\/span><span class=\"p\">.<\/span><span class=\"n\">visit<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"n\">url<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"c1\">\/\/ Increase reference counts.<\/span>\n<span class=\"w\">        <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">tx<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">tx<\/span><span class=\"p\">.<\/span><span class=\"n\">clone<\/span><span class=\"p\">();<\/span>\n<span class=\"w\">        <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">sub_pool<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">pool<\/span><span class=\"p\">.<\/span><span class=\"n\">clone<\/span><span class=\"p\">();<\/span>\n<span class=\"w\">        <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">client<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">client<\/span><span class=\"p\">.<\/span><span class=\"n\">clone<\/span><span class=\"p\">();<\/span>\n\n<span class=\"w\">        <\/span><span class=\"c1\">\/\/ Submit the work.<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">pool<\/span><span class=\"p\">.<\/span><span class=\"n\">execute<\/span><span class=\"p\">(<\/span><span class=\"k\">move<\/span><span class=\"w\"> <\/span><span class=\"o\">||<\/span><span class=\"w\"> <\/span><span class=\"k\">match<\/span><span class=\"w\"> <\/span><span class=\"n\">http_list<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"n\">client<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">url<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">            <\/span><span class=\"c1\">\/\/ We decoded some urls.<\/span>\n<span class=\"w\">            <\/span><span class=\"nb\">Ok<\/span><span class=\"p\">(<\/span><span class=\"n\">urls<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"o\">=&gt;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                <\/span><span class=\"k\">for<\/span><span class=\"w\"> <\/span><span class=\"n\">url<\/span><span class=\"w\"> <\/span><span class=\"k\">in<\/span><span class=\"w\"> <\/span><span class=\"n\">urls<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                    <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"n\">url<\/span><span class=\"p\">.<\/span><span class=\"n\">path<\/span><span class=\"p\">().<\/span><span class=\"n\">ends_with<\/span><span class=\"p\">(<\/span><span class=\"sc\">&#39;\/&#39;<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                        <\/span><span class=\"c1\">\/\/ Recursively call the handler on sub directory.<\/span>\n<span class=\"w\">                        <\/span><span class=\"n\">Crawler<\/span><span class=\"p\">::<\/span><span class=\"n\">process<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"n\">visitor<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"n\">client<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"n\">sub_pool<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"n\">tx<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">url<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">                    <\/span><span class=\"p\">}<\/span><span class=\"w\"> <\/span><span class=\"k\">else<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">                        <\/span><span class=\"c1\">\/\/ Send file location to the mpsc channel.<\/span>\n<span class=\"w\">                        <\/span><span class=\"n\">tx<\/span><span class=\"p\">.<\/span><span class=\"n\">send<\/span><span class=\"p\">(<\/span><span class=\"nb\">Some<\/span><span class=\"p\">(<\/span><span class=\"nb\">Ok<\/span><span class=\"p\">(<\/span><span class=\"n\">url<\/span><span class=\"p\">))).<\/span><span class=\"n\">unwrap<\/span><span class=\"p\">()<\/span>\n<span class=\"w\">                    <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">                <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">                <\/span><span class=\"c1\">\/\/ Indicate we are done.<\/span>\n<span class=\"w\">                <\/span><span class=\"n\">tx<\/span><span class=\"p\">.<\/span><span class=\"n\">send<\/span><span class=\"p\">(<\/span><span class=\"nb\">None<\/span><span class=\"p\">).<\/span><span class=\"n\">unwrap<\/span><span class=\"p\">()<\/span>\n<span class=\"w\">            <\/span><span class=\"p\">}<\/span>\n\n<span class=\"w\">            <\/span><span class=\"c1\">\/\/ An error happened, propagates it.<\/span>\n<span class=\"w\">            <\/span><span class=\"nb\">Err<\/span><span class=\"p\">(<\/span><span class=\"n\">e<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"o\">=&gt;<\/span><span class=\"w\"> <\/span><span class=\"n\">tx<\/span><span class=\"p\">.<\/span><span class=\"n\">send<\/span><span class=\"p\">(<\/span><span class=\"nb\">Some<\/span><span class=\"p\">(<\/span><span class=\"nb\">Err<\/span><span class=\"p\">(<\/span><span class=\"n\">e<\/span><span class=\"p\">))).<\/span><span class=\"n\">unwrap<\/span><span class=\"p\">(),<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">});<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<p>You can check the complete implementation in the <a class=\"reference external\" href=\"https:\/\/github.com\/logreduce\/logreduce-rust\/blob\/main\/httpdir\/src\/httpdir.rs\">httpdir.rs<\/a> module.<\/p>\n<\/div>\n<div class=\"section\" id=\"completing-my-first-project-in-rust\">\n<h2>Completing my first project in Rust<\/h2>\n<p>Logreduce is the first project that I wrote using Rust. Here are my initial impressions of the language.<\/p>\n<p>Pros:<\/p>\n<ul class=\"simple\">\n<li>Reliable: When working on the model processor, I went through multiple iterations, and the code worked after it compiled everytime.<\/li>\n<li>Network effect: The language attracts many talented developers. For example, the <a class=\"reference external\" href=\"https:\/\/docs.rs\/regex\">regex<\/a> and <a class=\"reference external\" href=\"https:\/\/hyper.rs\">hyper<\/a> crates look amazing.<\/li>\n<li>Stellar toolchain: Everything looks tightly integrated and snappy. I particularly enjoy the workspace feature to structure the code base in multiple libraries with their own dependencies.<\/li>\n<\/ul>\n<p>Cons:<\/p>\n<ul class=\"simple\">\n<li>Lifetimes are notoriously difficult to understand and I avoided them as much as possible to keep the code simple.<\/li>\n<li>Macros are appealing but they can be rather cryptic and hard to debug.<\/li>\n<li>Sometimes the type inference does not work and it needs extra annotations. For example, to convert a list of result to a result list we can use the turbofish syntax: <em>collect::&lt;Result&lt;Vec&lt;_&gt;&gt;&gt;()<\/em>. In Haskell, this is implemented with the <em>traverse :: (a -&gt; f b) -&gt; t a -&gt; f (t b)<\/em> function, which I find less complicated.<\/li>\n<\/ul>\n<p>I am mainly interested in Rust's expressive static types. They generally work the same as in Haskell and OCaml, or any other language featuring <a class=\"reference external\" href=\"https:\/\/doc.rust-lang.org\/book\/ch06-00-enums.html\">Algebraic Data Types<\/a>.\nSuch types let me fearlessly perform heart surgery on complex code.\nAs explained in the <a class=\"reference external\" href=\"https:\/\/www.youtube.com\/watch?v=kZ1P8cHN3pY\">Why Functional Programming Doesn't Matter<\/a> talk, expressive static types give us the dexterity to extend our system in a fairly safe way.\nIn particular, by making illegal states unrepresentable, we don't have to worry about many kinds of errors.\nThe type system statically verifies a significant part of our program, enabling us to move fast by focusing on the most important part.<\/p>\n<\/div>\n<div class=\"section\" id=\"conclusion\">\n<h2>Conclusion<\/h2>\n<p>Coming from Haskell, the main challenge of using Rust is to be more careful about the values and their memory.\nAnd after going through the initial bumps, I must say it's getting a little easier and I now mostly understand what the compiler wants.<\/p>\n<p>The Rust implementation of logreduce is now almost feature complete with the legacy Python code, and I'm looking forward adding the last remaining parts:<\/p>\n<ul class=\"simple\">\n<li>Discovery of baselines for CI build.<\/li>\n<li>Supporting systemd-journal sources.<\/li>\n<li>Handling tarball transparently.<\/li>\n<\/ul>\n<p>I always welcome feedback, if you would like to contribute, please join the <a class=\"reference external\" href=\"https:\/\/matrix.to\/#\/#logreduce:matrix.org\">#logreduce:matrix.org<\/a> chat room.<\/p>\n<p>Thank you for reading!<\/p>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Zuul Security Fix Ansible plugin loading","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/zuul-security-fix-ansible-plugin-loading.html","rel":"alternate"}},"published":"2022-03-25T00:00:00+00:00","updated":"2022-03-25T00:00:00+00:00","author":{"name":"sf"},"id":"tag:www.softwarefactory-project.io,2022-03-25:\/zuul-security-fix-ansible-plugin-loading.html","summary":"<p>A new Zuul version has been added to the SF-3.6 to address\na security issue: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/835121\">https:\/\/review.opendev.org\/835121<\/a>.\nTo fix a deployment run <strong>sfconfig --update<\/strong> from the\ninstall-server. Alternatively, run:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"c1\"># From the install-server<\/span>\nansible<span class=\"w\"> <\/span>-m<span class=\"w\"> <\/span><span class=\"nb\">command<\/span><span class=\"w\"> <\/span>-a<span class=\"w\"> <\/span><span class=\"s2\">&quot;yum update -y *zuul*&quot;<\/span><span class=\"w\"> <\/span>zuul-scheduler:zuul-web:zuul-executor:zuul-merger\nansible-playbook<span class=\"w\"> <\/span>\/var \u2026<\/pre><\/div>","content":"<p>A new Zuul version has been added to the SF-3.6 to address\na security issue: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/835121\">https:\/\/review.opendev.org\/835121<\/a>.\nTo fix a deployment run <strong>sfconfig --update<\/strong> from the\ninstall-server. Alternatively, run:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"c1\"># From the install-server<\/span>\nansible<span class=\"w\"> <\/span>-m<span class=\"w\"> <\/span><span class=\"nb\">command<\/span><span class=\"w\"> <\/span>-a<span class=\"w\"> <\/span><span class=\"s2\">&quot;yum update -y *zuul*&quot;<\/span><span class=\"w\"> <\/span>zuul-scheduler:zuul-web:zuul-executor:zuul-merger\nansible-playbook<span class=\"w\"> <\/span>\/var\/lib\/software-factory\/ansible\/zuul_restart.yml\n<\/pre><\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Introducing the BytesLines iterator","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/introducing-the-byteslines-iterator.html","rel":"alternate"}},"published":"2022-03-16T00:00:00+00:00","updated":"2022-03-16T00:00:00+00:00","author":{"name":"tristanC"},"id":"tag:www.softwarefactory-project.io,2022-03-16:\/introducing-the-byteslines-iterator.html","summary":"<p>The BytesLines iterator's goal is to provide an API for processing logs line by line.\nIt processes logs by:<\/p>\n<ul class=\"simple\">\n<li>Splitting sub line to treat cmd output embedded as a long oneliner.<\/li>\n<li>Working with Read objects, such as file decompressors or network endpoints.<\/li>\n<li>Using zero copy slices to optimize memory usage \u2026<\/li><\/ul>","content":"<p>The BytesLines iterator's goal is to provide an API for processing logs line by line.\nIt processes logs by:<\/p>\n<ul class=\"simple\">\n<li>Splitting sub line to treat cmd output embedded as a long oneliner.<\/li>\n<li>Working with Read objects, such as file decompressors or network endpoints.<\/li>\n<li>Using zero copy slices to optimize memory usage.<\/li>\n<li>Limiting line length to prevent overflow of invalid data.<\/li>\n<\/ul>\n<p>This blog post presents:<\/p>\n<ul class=\"simple\">\n<li>Evaluation criterias to compare different implementations.<\/li>\n<li>A simple implementation using readline.<\/li>\n<li>Why iterators can't easily produce pointers.<\/li>\n<li>A zero copy implementation using the <a class=\"reference external\" href=\"https:\/\/docs.rs\/bytes\/\">bytes<\/a> library.<\/li>\n<\/ul>\n<div class=\"admonition note\">\n<p class=\"first admonition-title\">Note<\/p>\n<p>This article is part of a blog post series about the latest logreduce improvements\nusing the Rust programing language. Please see the series' earlier articles:<\/p>\n<ul class=\"last simple\">\n<li>Part1: <a class=\"reference external\" href=\"https:\/\/www.softwarefactory-project.io\/improving-logreduce-with-rust.html\">Improving logreduce tokenizer<\/a>.<\/li>\n<li>Part2: <a class=\"reference external\" href=\"https:\/\/www.softwarefactory-project.io\/implementing-logreduce-nearest-neighbors-model-in-rust.html\">Implementing logreduce nearest neighbors model<\/a>.<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"evaluation-criterias\">\n<h2>Evaluation criterias<\/h2>\n<p>I evaluate the execution time and memory usage to process a 91MB file.\nPerformances are measured with:<\/p>\n<ul class=\"simple\">\n<li><cite>\/bin\/time -v<\/cite> to measure the maximum memory usage.<\/li>\n<li><cite>valgrind<\/cite> to collect heap usage.<\/li>\n<\/ul>\n<p>For example, <cite>grep<\/cite> performance is:<\/p>\n<div class=\"highlight\"><pre><span><\/span>$ \/bin\/time --format &quot;Run time: %e sec, Max RSS: %M KB&quot; grep anomaly &lt; test.txt\nRun time: 0.05 sec, Max RSS: 2380 KB\n\n$ valgrind grep &quot;anomaly&quot; &lt; test.txt |&amp; grep &quot;heap usage&quot;\ntotal heap usage: 305 allocs, 265 frees, 146,413 bytes allocated\n<\/pre><\/div>\n<p>Grep takes about 50 msec and it needs a bit more than 2MB of memory to do its job.\nValgrind shows a reasonable heap usage, confirming that grep is well optimized.<\/p>\n<p>The next sections present different implementations for the BytesLines iterator.<\/p>\n<\/div>\n<div class=\"section\" id=\"readline-iterator\">\n<h2>Readline iterator<\/h2>\n<p>One of the main goals is to avoid reading the whole file at once.\nInstead, the lines are loaded one at a time using readline.\nHere is a basic implementation in Python:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"k\">def<\/span> <span class=\"nf\">logfile_iterator<\/span><span class=\"p\">(<\/span><span class=\"n\">reader<\/span><span class=\"p\">):<\/span>\n    <span class=\"n\">line_number<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span>\n    <span class=\"k\">while<\/span> <span class=\"kc\">True<\/span><span class=\"p\">:<\/span>\n        <span class=\"n\">line<\/span> <span class=\"o\">=<\/span> <span class=\"n\">reader<\/span><span class=\"o\">.<\/span><span class=\"n\">readline<\/span><span class=\"p\">()<\/span>\n        <span class=\"k\">if<\/span> <span class=\"ow\">not<\/span> <span class=\"n\">line<\/span><span class=\"p\">:<\/span>\n            <span class=\"k\">break<\/span>\n        <span class=\"n\">line<\/span> <span class=\"o\">=<\/span> <span class=\"n\">line<\/span><span class=\"o\">.<\/span><span class=\"n\">rstrip<\/span><span class=\"p\">()<\/span>\n        <span class=\"n\">line_number<\/span> <span class=\"o\">+=<\/span> <span class=\"mi\">1<\/span>\n        <span class=\"k\">for<\/span> <span class=\"n\">subline<\/span> <span class=\"ow\">in<\/span> <span class=\"n\">line<\/span><span class=\"o\">.<\/span><span class=\"n\">split<\/span><span class=\"p\">(<\/span><span class=\"s2\">&quot;<\/span><span class=\"se\">\\\\<\/span><span class=\"s2\">n&quot;<\/span><span class=\"p\">):<\/span>\n            <span class=\"k\">yield<\/span> <span class=\"p\">(<\/span><span class=\"n\">subline<\/span><span class=\"p\">,<\/span> <span class=\"n\">line_number<\/span><span class=\"p\">)<\/span>\n<\/pre><\/div>\n<p>And here is an equivalent implementation in Rust:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"sd\">\/\/\/ A struct to hold the state of the iterator.<\/span>\n<span class=\"k\">pub<\/span><span class=\"w\"> <\/span><span class=\"k\">struct<\/span><span class=\"w\"> <\/span><span class=\"nc\">BufLines<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">R<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nc\">Read<\/span><span class=\"o\">&gt;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">reader<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nc\">BufReader<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">R<\/span><span class=\"o\">&gt;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">buffer<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nb\">String<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">line_number<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"kt\">usize<\/span><span class=\"p\">,<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"k\">impl<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">R<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nc\">Read<\/span><span class=\"o\">&gt;<\/span><span class=\"w\"> <\/span><span class=\"nb\">Iterator<\/span><span class=\"w\"> <\/span><span class=\"k\">for<\/span><span class=\"w\"> <\/span><span class=\"n\">BufLines<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">R<\/span><span class=\"o\">&gt;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">type<\/span><span class=\"w\"> <\/span><span class=\"nc\">Item<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"nb\">Result<\/span><span class=\"o\">&lt;<\/span><span class=\"p\">(<\/span><span class=\"nb\">String<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"kt\">usize<\/span><span class=\"p\">)<\/span><span class=\"o\">&gt;<\/span><span class=\"p\">;<\/span>\n\n<span class=\"w\">    <\/span><span class=\"k\">fn<\/span><span class=\"w\"> <\/span><span class=\"nf\">next<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"k\">mut<\/span><span class=\"w\"> <\/span><span class=\"bp\">self<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"nb\">Option<\/span><span class=\"o\">&lt;<\/span><span class=\"bp\">Self<\/span><span class=\"p\">::<\/span><span class=\"n\">Item<\/span><span class=\"o\">&gt;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"bp\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">buffer<\/span><span class=\"p\">.<\/span><span class=\"n\">is_empty<\/span><span class=\"p\">()<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">            <\/span><span class=\"bp\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">read_line<\/span><span class=\"p\">()<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span><span class=\"w\"> <\/span><span class=\"k\">else<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">            <\/span><span class=\"nb\">Some<\/span><span class=\"p\">(<\/span><span class=\"nb\">Ok<\/span><span class=\"p\">(<\/span><span class=\"bp\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">get_line<\/span><span class=\"p\">()))<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<p>Notice that Rust doesn't yet have a special keyword to <em>yield<\/em> a value.\nWe need to maintain the context in a structure that is passed each time\nthe <em>next<\/em> method is called. Here are the two functions of this iterator:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"c1\">\/\/ Read a new line and call get_line<\/span>\n<span class=\"k\">fn<\/span><span class=\"w\"> <\/span><span class=\"nf\">read_line<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"k\">mut<\/span><span class=\"w\"> <\/span><span class=\"bp\">self<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"nb\">Option<\/span><span class=\"o\">&lt;<\/span><span class=\"nb\">Result<\/span><span class=\"o\">&lt;<\/span><span class=\"p\">(<\/span><span class=\"nb\">String<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"kt\">usize<\/span><span class=\"p\">)<\/span><span class=\"o\">&gt;&gt;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">match<\/span><span class=\"w\"> <\/span><span class=\"bp\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">reader<\/span><span class=\"p\">.<\/span><span class=\"n\">read_line<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"k\">mut<\/span><span class=\"w\"> <\/span><span class=\"bp\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">buffer<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"nb\">Ok<\/span><span class=\"p\">(<\/span><span class=\"n\">n<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"n\">n<\/span><span class=\"w\"> <\/span><span class=\"o\">&gt;<\/span><span class=\"w\"> <\/span><span class=\"mi\">0<\/span><span class=\"w\"> <\/span><span class=\"o\">=&gt;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">            <\/span><span class=\"c1\">\/\/ The read succeeded<\/span>\n<span class=\"w\">            <\/span><span class=\"bp\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">buffer<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"bp\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">buffer<\/span><span class=\"p\">.<\/span><span class=\"n\">trim_end<\/span><span class=\"p\">().<\/span><span class=\"n\">to_owned<\/span><span class=\"p\">();<\/span>\n<span class=\"w\">            <\/span><span class=\"nb\">Some<\/span><span class=\"p\">(<\/span><span class=\"nb\">Ok<\/span><span class=\"p\">(<\/span><span class=\"bp\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">get_line<\/span><span class=\"p\">()))<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">        <\/span><span class=\"nb\">Ok<\/span><span class=\"p\">(<\/span><span class=\"n\">_<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"o\">=&gt;<\/span><span class=\"w\"> <\/span><span class=\"nb\">None<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">        <\/span><span class=\"nb\">Err<\/span><span class=\"p\">(<\/span><span class=\"n\">e<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"o\">=&gt;<\/span><span class=\"w\"> <\/span><span class=\"nb\">Some<\/span><span class=\"p\">(<\/span><span class=\"nb\">Err<\/span><span class=\"p\">(<\/span><span class=\"n\">e<\/span><span class=\"p\">)),<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"c1\">\/\/ Return the first sub line found in the buffer.<\/span>\n<span class=\"k\">fn<\/span><span class=\"w\"> <\/span><span class=\"nf\">get_line<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"k\">mut<\/span><span class=\"w\"> <\/span><span class=\"bp\">self<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"nb\">String<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"kt\">usize<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">line<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"nb\">Some<\/span><span class=\"p\">((<\/span><span class=\"n\">sub_line<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">rest<\/span><span class=\"p\">))<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"bp\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">buffer<\/span><span class=\"p\">.<\/span><span class=\"n\">split_once<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;<\/span><span class=\"se\">\\\\<\/span><span class=\"s\">n&quot;<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">sub_line<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">sub_line<\/span><span class=\"p\">.<\/span><span class=\"n\">clone<\/span><span class=\"p\">();<\/span>\n<span class=\"w\">        <\/span><span class=\"bp\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">buffer<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">rest<\/span><span class=\"p\">.<\/span><span class=\"n\">to_owned<\/span><span class=\"p\">();<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">sub_line<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span><span class=\"w\"> <\/span><span class=\"k\">else<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"bp\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">line_number<\/span><span class=\"w\"> <\/span><span class=\"o\">+=<\/span><span class=\"w\"> <\/span><span class=\"mi\">1<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">        <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">line<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"bp\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">buffer<\/span><span class=\"p\">.<\/span><span class=\"n\">clone<\/span><span class=\"p\">();<\/span>\n<span class=\"w\">        <\/span><span class=\"bp\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">buffer<\/span><span class=\"p\">.<\/span><span class=\"n\">clear<\/span><span class=\"p\">();<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">line<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">};<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">(<\/span><span class=\"n\">line<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"bp\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">line_number<\/span><span class=\"p\">)<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<p>Both of these implementations are using the same algorithm, by calling the <em>readline()<\/em> helper\nbefore splitting sub line on litteral <em>&quot;\\n&quot;<\/em>.<\/p>\n<p>Here are their performance characteristics using <em>python3-3.10.2-1.fc35.x86_64<\/em> and <em>rustc-1.52.1<\/em>:<\/p>\n<table border=\"1\" class=\"docutils\">\n<colgroup>\n<col width=\"21%\" \/>\n<col width=\"13%\" \/>\n<col width=\"15%\" \/>\n<col width=\"15%\" \/>\n<col width=\"23%\" \/>\n<col width=\"13%\" \/>\n<\/colgroup>\n<thead valign=\"bottom\">\n<tr><th class=\"head\">Implementation<\/th>\n<th class=\"head\">Max RSS<\/th>\n<th class=\"head\">Allocs<\/th>\n<th class=\"head\">Frees<\/th>\n<th class=\"head\">Bytes allocated<\/th>\n<th class=\"head\">Run time<\/th>\n<\/tr>\n<\/thead>\n<tbody valign=\"top\">\n<tr><td>readline.py<\/td>\n<td>7420 KB<\/td>\n<td>1,814,409<\/td>\n<td>1,810,434<\/td>\n<td>475,434,838<\/td>\n<td>0.33 sec<\/td>\n<\/tr>\n<tr><td>readline.rs<\/td>\n<td>2260 KB<\/td>\n<td>692,114<\/td>\n<td>692,112<\/td>\n<td>285,799,923<\/td>\n<td>0.15 sec<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<ul class=\"simple\">\n<li>Both implementations work in constant memory. Using a bigger file does not increase the Max RSS value.<\/li>\n<li>The high heap allocations numbers indicate that each individual line is duplicated.<\/li>\n<li>Rust code is more verbose, but it performs more efficiently and safely because it wraps each line with a Result data type to avoid throwing exceptions.<\/li>\n<\/ul>\n<p>The next sections present a technique to reduce the number of allocations.<\/p>\n<\/div>\n<div class=\"section\" id=\"iterator-and-item-lifetime\">\n<h2>Iterator and item lifetime<\/h2>\n<p>Rust provides facilities for manual memory management, thus it should be possible to avoid the individual line allocation.\nThe line is already present in the iterator internal structure, and instead of cloning a new <em>String<\/em> I would like to return\na <em>&amp;str<\/em> reference.<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"k\">impl<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">R<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nc\">Read<\/span><span class=\"o\">&gt;<\/span><span class=\"w\"> <\/span><span class=\"nb\">Iterator<\/span><span class=\"w\"> <\/span><span class=\"k\">for<\/span><span class=\"w\"> <\/span><span class=\"n\">BufLines<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">R<\/span><span class=\"o\">&gt;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">   <\/span><span class=\"k\">type<\/span><span class=\"w\"> <\/span><span class=\"nc\">Item<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"nb\">Result<\/span><span class=\"o\">&lt;&amp;<\/span><span class=\"kt\">str<\/span><span class=\"o\">&gt;<\/span><span class=\"p\">;<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<p>\u2026 but this does not compile because of this error:<\/p>\n<div class=\"highlight\"><pre><span><\/span>error[E0106]: missing lifetime specifier\n  --&gt; readline.rs:17:24\n   |\n17 |     type Item = Result&lt;&amp;str&gt;;\n   |                        ^ expected named lifetime parameter\n   |\nhelp: consider introducing a named lifetime parameter\n   |\n17 |     type Item&lt;&#39;a&gt; = Result&lt;&amp;&#39;a str&gt;;\n   |              ^^^^          ^^^\n<\/pre><\/div>\n<p>Indeed, the <em>&amp;str<\/em> reference needs a lifetime parameter to match the owner of the underlying memory.\nThis lifetime parameter is here to ensure the reference is valid as long as the underlying memory is owned.\nUnfortunately, adding the suggested fix does not work:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"k\">impl<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">R<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nc\">Read<\/span><span class=\"o\">&gt;<\/span><span class=\"w\"> <\/span><span class=\"nb\">Iterator<\/span><span class=\"w\"> <\/span><span class=\"k\">for<\/span><span class=\"w\"> <\/span><span class=\"n\">BufLines<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">R<\/span><span class=\"o\">&gt;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">type<\/span><span class=\"w\"> <\/span><span class=\"nc\">Item<\/span><span class=\"o\">&lt;&#39;<\/span><span class=\"na\">a<\/span><span class=\"o\">&gt;<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"nb\">Result<\/span><span class=\"o\">&lt;&amp;&#39;<\/span><span class=\"na\">a<\/span><span class=\"w\"> <\/span><span class=\"kt\">str<\/span><span class=\"o\">&gt;<\/span><span class=\"p\">;<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<p>\u2026 the compilation still fails because of this new error:<\/p>\n<div class=\"highlight\"><pre><span><\/span>error[E0658]: generic associated types are unstable\n  --&gt; readline.rs:17:5\n   |\n17 |     type Item&lt;&#39;a&gt; = Result&lt;&amp;&#39;a str&gt;;\n   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n   |\n   = note: see issue #44265 &lt;https:\/\/github.com\/rust-lang\/rust\/issues\/44265&gt; for more information\n<\/pre><\/div>\n<p>The Rust type system is presently not expressive enough to implement such an iterator.\nYou can read more about this limitation in this article:\n<a class=\"reference external\" href=\"http:\/\/lukaskalbertodt.github.io\/2018\/08\/03\/solving-the-generalized-streaming-iterator-problem-without-gats.html\">Solving the Generalized Streaming Iterator Problem without GATs<\/a>.<\/p>\n<p>Even then, it is unclear how the users of this iterator would be able to keep that reference\nafter the iteration. This is a requirement for logreduce's reports to include the surrounding anomalies' context.<\/p>\n<p>The next section presents an alternative solution using the <a class=\"reference external\" href=\"https:\/\/docs.rs\/bytes\/\">bytes<\/a> library.<\/p>\n<\/div>\n<div class=\"section\" id=\"byteslines-iterator\">\n<h2>BytesLines iterator<\/h2>\n<p>The <a class=\"reference external\" href=\"https:\/\/docs.rs\/bytes\/\">bytes<\/a> library provides a data type which bundles the reference with the underlying buffer using\na reference counter. You can read more about its implementation in the <a class=\"reference external\" href=\"https:\/\/docs.rs\/bytes\/latest\/bytes\/struct.Bytes.html#memory-layout\">Bytes memory layout<\/a> documentation.<\/p>\n<p>This lets us return the line location without doing any memory copy, at\nthe cost of a slight overhead, to keep track of the size and pointer's owners.\nHere is how the BytesLines iterator is defined:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"sd\">\/\/\/ The BytesLines struct holds a single buffer<\/span>\n<span class=\"k\">pub<\/span><span class=\"w\"> <\/span><span class=\"k\">struct<\/span><span class=\"w\"> <\/span><span class=\"nc\">BytesLines<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">R<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nc\">Read<\/span><span class=\"o\">&gt;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">reader<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nc\">R<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">buf<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nc\">BytesMut<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">line_count<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"kt\">usize<\/span><span class=\"p\">,<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"k\">impl<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">R<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nc\">Read<\/span><span class=\"o\">&gt;<\/span><span class=\"w\"> <\/span><span class=\"nb\">Iterator<\/span><span class=\"w\"> <\/span><span class=\"k\">for<\/span><span class=\"w\"> <\/span><span class=\"n\">BytesLines<\/span><span class=\"o\">&lt;<\/span><span class=\"n\">R<\/span><span class=\"o\">&gt;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">type<\/span><span class=\"w\"> <\/span><span class=\"nc\">Item<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"nb\">Result<\/span><span class=\"o\">&lt;<\/span><span class=\"p\">(<\/span><span class=\"n\">Bytes<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"kt\">usize<\/span><span class=\"p\">)<\/span><span class=\"o\">&gt;<\/span><span class=\"p\">;<\/span>\n\n<span class=\"w\">    <\/span><span class=\"k\">fn<\/span><span class=\"w\"> <\/span><span class=\"nf\">next<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"k\">mut<\/span><span class=\"w\"> <\/span><span class=\"bp\">self<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"nb\">Option<\/span><span class=\"o\">&lt;<\/span><span class=\"bp\">Self<\/span><span class=\"p\">::<\/span><span class=\"n\">Item<\/span><span class=\"o\">&gt;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"bp\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">buf<\/span><span class=\"p\">.<\/span><span class=\"n\">is_empty<\/span><span class=\"p\">()<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">            <\/span><span class=\"bp\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">read_slice<\/span><span class=\"p\">()<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span><span class=\"w\"> <\/span><span class=\"k\">else<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">            <\/span><span class=\"bp\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">get_slice<\/span><span class=\"p\">()<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<p>Then, similarly to the previous readline implementation, this iterator uses two main functions:<\/p>\n<ul class=\"simple\">\n<li><em>read_slice<\/em> to fill up the buffer.<\/li>\n<li><em>get_slice<\/em> to split the next line.<\/li>\n<\/ul>\n<div class=\"highlight\"><pre><span><\/span><span class=\"c1\">\/\/ Read a new chunk and call get_slice<\/span>\n<span class=\"k\">fn<\/span><span class=\"w\"> <\/span><span class=\"nf\">read_slice<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"k\">mut<\/span><span class=\"w\"> <\/span><span class=\"bp\">self<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"nb\">Option<\/span><span class=\"o\">&lt;<\/span><span class=\"nb\">Result<\/span><span class=\"o\">&lt;<\/span><span class=\"p\">(<\/span><span class=\"n\">Bytes<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"kt\">usize<\/span><span class=\"p\">)<\/span><span class=\"o\">&gt;&gt;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">pos<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"bp\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">buf<\/span><span class=\"p\">.<\/span><span class=\"n\">len<\/span><span class=\"p\">();<\/span>\n<span class=\"w\">    <\/span><span class=\"bp\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">buf<\/span><span class=\"p\">.<\/span><span class=\"n\">resize<\/span><span class=\"p\">(<\/span><span class=\"n\">pos<\/span><span class=\"w\"> <\/span><span class=\"o\">+<\/span><span class=\"w\"> <\/span><span class=\"n\">CHUNK_SIZE<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"mi\">0<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">match<\/span><span class=\"w\"> <\/span><span class=\"bp\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">reader<\/span><span class=\"p\">.<\/span><span class=\"n\">read<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"k\">mut<\/span><span class=\"w\"> <\/span><span class=\"bp\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">buf<\/span><span class=\"p\">[<\/span><span class=\"n\">pos<\/span><span class=\"o\">..<\/span><span class=\"p\">])<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"c1\">\/\/ We read some data.<\/span>\n<span class=\"w\">        <\/span><span class=\"nb\">Ok<\/span><span class=\"p\">(<\/span><span class=\"n\">n<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"n\">n<\/span><span class=\"w\"> <\/span><span class=\"o\">&gt;<\/span><span class=\"w\"> <\/span><span class=\"mi\">0<\/span><span class=\"w\"> <\/span><span class=\"o\">=&gt;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">            <\/span><span class=\"bp\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">buf<\/span><span class=\"p\">.<\/span><span class=\"n\">truncate<\/span><span class=\"p\">(<\/span><span class=\"n\">pos<\/span><span class=\"w\"> <\/span><span class=\"o\">+<\/span><span class=\"w\"> <\/span><span class=\"n\">n<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">            <\/span><span class=\"bp\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">get_slice<\/span><span class=\"p\">()<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span>\n\n<span class=\"w\">        <\/span><span class=\"c1\">\/\/ We reached the end of the reader, this is the end.<\/span>\n<span class=\"w\">        <\/span><span class=\"nb\">Ok<\/span><span class=\"p\">(<\/span><span class=\"n\">_<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"o\">=&gt;<\/span><span class=\"w\"> <\/span><span class=\"nb\">None<\/span><span class=\"p\">,<\/span>\n\n<span class=\"w\">        <\/span><span class=\"c1\">\/\/ There was a reading error, we return it.<\/span>\n<span class=\"w\">        <\/span><span class=\"nb\">Err<\/span><span class=\"p\">(<\/span><span class=\"n\">e<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"o\">=&gt;<\/span><span class=\"w\"> <\/span><span class=\"nb\">Some<\/span><span class=\"p\">(<\/span><span class=\"nb\">Err<\/span><span class=\"p\">(<\/span><span class=\"n\">e<\/span><span class=\"p\">)),<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"c1\">\/\/ Find the next line in the buffer<\/span>\n<span class=\"k\">fn<\/span><span class=\"w\"> <\/span><span class=\"nf\">get_slice<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"k\">mut<\/span><span class=\"w\"> <\/span><span class=\"bp\">self<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"nb\">Option<\/span><span class=\"o\">&lt;<\/span><span class=\"nb\">Result<\/span><span class=\"o\">&lt;<\/span><span class=\"p\">(<\/span><span class=\"n\">Bytes<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"kt\">usize<\/span><span class=\"p\">)<\/span><span class=\"o\">&gt;&gt;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">match<\/span><span class=\"w\"> <\/span><span class=\"bp\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">find_next_line<\/span><span class=\"p\">()<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"c1\">\/\/ We haven&#39;t found the end of the line, we need more data.<\/span>\n<span class=\"w\">        <\/span><span class=\"nb\">None<\/span><span class=\"w\"> <\/span><span class=\"o\">=&gt;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">            <\/span><span class=\"c1\">\/\/ reserve() will attempt to reclaim space in the buffer.<\/span>\n<span class=\"w\">            <\/span><span class=\"bp\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">buf<\/span><span class=\"p\">.<\/span><span class=\"n\">reserve<\/span><span class=\"p\">(<\/span><span class=\"n\">CHUNK_SIZE<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">            <\/span><span class=\"bp\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">read_slice<\/span><span class=\"p\">()<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span>\n\n<span class=\"w\">        <\/span><span class=\"c1\">\/\/ We found the end of the line, we can return it now.<\/span>\n<span class=\"w\">        <\/span><span class=\"nb\">Some<\/span><span class=\"p\">((<\/span><span class=\"n\">pos<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">sep<\/span><span class=\"p\">))<\/span><span class=\"w\"> <\/span><span class=\"o\">=&gt;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">            <\/span><span class=\"c1\">\/\/ split_to() creates a new zero copy reference to the buffer.<\/span>\n<span class=\"w\">            <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">res<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"bp\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">buf<\/span><span class=\"p\">.<\/span><span class=\"n\">split_to<\/span><span class=\"p\">(<\/span><span class=\"n\">pos<\/span><span class=\"p\">).<\/span><span class=\"n\">freeze<\/span><span class=\"p\">();<\/span>\n<span class=\"w\">            <\/span><span class=\"bp\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">buf<\/span><span class=\"p\">.<\/span><span class=\"n\">advance<\/span><span class=\"p\">(<\/span><span class=\"n\">sep<\/span><span class=\"p\">.<\/span><span class=\"n\">len<\/span><span class=\"p\">());<\/span>\n<span class=\"w\">            <\/span><span class=\"nb\">Some<\/span><span class=\"p\">(<\/span><span class=\"nb\">Ok<\/span><span class=\"p\">((<\/span><span class=\"n\">res<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"bp\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">line_count<\/span><span class=\"p\">)))<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<p>By carefully managing this single buffer, the <a class=\"reference external\" href=\"https:\/\/docs.rs\/bytes\/\">bytes<\/a> library takes care of all the\nreferences counting and memory allocations.\nIn particular, the <a class=\"reference external\" href=\"https:\/\/docs.rs\/bytes\/latest\/bytes\/struct.BytesMut.html#method.reserve\">reserve<\/a> function will attempt to reclaim the available space in-place.<\/p>\n<p>Here is a sequence diagram for this implementation:<\/p>\n<div class=\"highlight\"><pre><span><\/span> \u2b69- the buffer starts here.\n[                          ]          &lt; the buffer is empty, we read a chunk.\n[aaaaaaaaaaaa\\nbbbbb\\nccccc]          &lt; there is a line separator.\n \u2570-----------\u2ba1 next slice\n              \u2b68\n[              bbbbb\\nccccc]\n               \u2570----\u2ba1 next slice\n                     \u2b68\n[                     ccccc]          &lt; the line is incomplete.\n      \u2b69 we reserve more space and move the left-overs at the begining of the buffer.\n[ccccc                           ]    &lt; we read another chunk after the left-overs.\n[ccccccc\\ndddddddddddddd\\neeeeeee]\n \u2570------\u2ba1 next slice\n         \u2b68\n[         dddddddddddddd\\neeeeeee]\n          \u2570-------------\u2ba1 next slice\n                         \u2b68\n[                         eeeeeee]    &lt; the line is incomplete.\n        \u2b69 we reserve more space and move the left-overs at the begining of the buffer.\n[eeeeeee                            ] &lt; we read another chunk after the left-overs.\n[eeeeeeeee\\n                        ] &lt; we reach the end of file.\n \u2570--------\u2ba1 the last slice\n<\/pre><\/div>\n<p>Here are the final results:<\/p>\n<table border=\"1\" class=\"docutils\">\n<colgroup>\n<col width=\"21%\" \/>\n<col width=\"13%\" \/>\n<col width=\"15%\" \/>\n<col width=\"15%\" \/>\n<col width=\"23%\" \/>\n<col width=\"13%\" \/>\n<\/colgroup>\n<thead valign=\"bottom\">\n<tr><th class=\"head\">Implementation<\/th>\n<th class=\"head\">Max RSS<\/th>\n<th class=\"head\">Allocs<\/th>\n<th class=\"head\">Frees<\/th>\n<th class=\"head\">Bytes allocated<\/th>\n<th class=\"head\">Run time<\/th>\n<\/tr>\n<\/thead>\n<tbody valign=\"top\">\n<tr><td>readline.py<\/td>\n<td>7420 KB<\/td>\n<td>1,814,409<\/td>\n<td>1,810,434<\/td>\n<td>475,434,838<\/td>\n<td>0.33 sec<\/td>\n<\/tr>\n<tr><td>readline.rs<\/td>\n<td>2260 KB<\/td>\n<td>692,114<\/td>\n<td>692,112<\/td>\n<td>285,799,923<\/td>\n<td>0.15 sec<\/td>\n<\/tr>\n<tr><td>byteslines.rs<\/td>\n<td>2068 KB<\/td>\n<td>24<\/td>\n<td>22<\/td>\n<td>265,577<\/td>\n<td>0.12 sec<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>As you can see, this iterator avoids unnecessary memory copy, and even though it does\nmore work to satisfy the borrow checker, it is still faster.<\/p>\n<p>You can find the source code of the benchmarks in the <a class=\"reference external\" href=\"https:\/\/github.com\/logreduce\/byteslines-demo\">logreduce\/byteslines-demo<\/a>\nproject, and you can see the complete version which includes a limiter for the line\nlength in the <a class=\"reference external\" href=\"https:\/\/github.com\/logreduce\/logreduce-rust\/blob\/main\/iterator\/src\/iterator.rs\">logreduce-iterator<\/a> library.<\/p>\n<\/div>\n<div class=\"section\" id=\"conclusion\">\n<h2>Conclusion<\/h2>\n<p>The Rust programming language provides low-level facilities and high-level features such\nas <a class=\"reference external\" href=\"https:\/\/doc.rust-lang.org\/book\/ch06-00-enums.html\">Algebraic Data Types<\/a> and <a class=\"reference external\" href=\"https:\/\/doc.rust-lang.org\/book\/ch10-02-traits.html\">Traits<\/a>. This lured me into trying to avoid cloning the memory and\nlearning more about Rust's unique type system.<\/p>\n<p>Thanks to the <a class=\"reference external\" href=\"https:\/\/docs.rs\/bytes\/\">bytes<\/a> library I was able to efficiently implement this log line iterator.\nI think it is well worth the effort since this is such a key component for the project,\nand I hope this is going to pay off when processing many files in parallel.<\/p>\n<p>I always welcome feedback, and I would love to be proven wrong. If you would like to contribute,\nplease join the <a class=\"reference external\" href=\"https:\/\/matrix.to\/#\/#logreduce:matrix.org\">#logreduce:matrix.org<\/a> chat room.<\/p>\n<p>Thank you for reading!<\/p>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2022 Feb 18 to Mar 09 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2022-feb-18-to-mar-09-summary.html","rel":"alternate"}},"published":"2022-03-09T10:00:00+00:00","updated":"2022-03-09T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2022-03-09:\/sprint-2022-feb-18-to-mar-09-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We propose &quot;logsender&quot; service that will be parsing log results fetched by Logscraper and send it to the Elasticsearch service. It change the log processing workflow by removing gearman-client, gearman-worker and logstash service<\/li>\n<li>We are adding more documentation \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We propose &quot;logsender&quot; service that will be parsing log results fetched by Logscraper and send it to the Elasticsearch service. It change the log processing workflow by removing gearman-client, gearman-worker and logstash service<\/li>\n<li>We are adding more documentation into ci log processing repository<\/li>\n<li>We submitted some fixes to zuul's web UI that we noticed when upgrading to zuul 5:<\/li>\n<li>Fix broken enqueue when buildset.newrev is null <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/830846\">https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/830846<\/a><\/li>\n<li>Hide Sign-In button if no Identity Provider is available <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/831222\">https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/831222<\/a><\/li>\n<li>Insist in documentation about adding a default HS256 authenticator to maintain CLI usefulness <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/831231\">https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/831231<\/a><\/li>\n<li>We proposed more authentication methods for zuul-client:<\/li>\n<li>basic auth <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul-client\/+\/819118\">https:\/\/review.opendev.org\/c\/zuul\/zuul-client\/+\/819118<\/a><\/li>\n<li>web auth <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul-client\/+\/831946\">https:\/\/review.opendev.org\/c\/zuul\/zuul-client\/+\/831946<\/a><\/li>\n<li>We worked on adding a quick tenant selector on the GUI for non-whitelabeled deployments; this requires caching the tenants list in the API<\/li>\n<li>We added support for <cite>[K<\/cite> and <cite>[m<\/cite> escape code in zuul console output: <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/re-ansi\/+\/24175\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/re-ansi\/+\/24175<\/a><\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul>\n<li><p class=\"first\">We investigated how to avoid extra user uid for the container by using uidmap.<\/p>\n<\/li>\n<li><p class=\"first\">We are working on SF 3.7 release and here the stories finished during the sprint:<\/p>\n<blockquote>\n<ul class=\"simple\">\n<li>We removed the patches action from sf-container role now we build our own zuul and nodepool containers<\/li>\n<li>We updated zuul-jobs package<\/li>\n<li>We updated managesf (which create hound configuration) and fixed zuul configuration to redirect to gitiles<\/li>\n<li>We run all containerized services with service user instead root<\/li>\n<li>We removed repoxplorer bits from managesf for the service is now deprecated<\/li>\n<li>We added documentation for operator to manage containers <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/cgit\/software-factory\/sf-docs\/tree\/docs\/operator\/deepdive.rst#n111\">https:\/\/softwarefactory-project.io\/cgit\/software-factory\/sf-docs\/tree\/docs\/operator\/deepdive.rst#n111<\/a><\/li>\n<li>We upgraded and containerized etherpad<\/li>\n<\/ul>\n<\/blockquote>\n<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Implementing logreduce nearest neighbors model in Rust","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/implementing-logreduce-nearest-neighbors-model-in-rust.html","rel":"alternate"}},"published":"2022-02-25T00:00:00+00:00","updated":"2022-02-25T00:00:00+00:00","author":{"name":"tristanC"},"id":"tag:www.softwarefactory-project.io,2022-02-25:\/implementing-logreduce-nearest-neighbors-model-in-rust.html","summary":"<p>This article is a follow-up on the previous post about <a class=\"reference external\" href=\"https:\/\/www.softwarefactory-project.io\/improving-logreduce-with-rust.html\">Improving logreduce with Rust<\/a>.\nWith the new tokenizer in place, the next step is to implement the nearest neighbors model.<\/p>\n<p>In this post you will learn the following about the core algorithm of logreduce:<\/p>\n<ul class=\"simple\">\n<li>Why vectorization is necessary.<\/li>\n<li>Cosine similarity \u2026<\/li><\/ul>","content":"<p>This article is a follow-up on the previous post about <a class=\"reference external\" href=\"https:\/\/www.softwarefactory-project.io\/improving-logreduce-with-rust.html\">Improving logreduce with Rust<\/a>.\nWith the new tokenizer in place, the next step is to implement the nearest neighbors model.<\/p>\n<p>In this post you will learn the following about the core algorithm of logreduce:<\/p>\n<ul class=\"simple\">\n<li>Why vectorization is necessary.<\/li>\n<li>Cosine similarity.<\/li>\n<li>How to compute the distances between two matrices.<\/li>\n<\/ul>\n<div class=\"section\" id=\"problem-statement\">\n<h2>Problem statement<\/h2>\n<p>Given two log files: a baseline and a target, the goal is to extract useful information from the target by finding the log lines that don't occur in the baseline.\nFor example, here is a simple solution implementation:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"k\">for<\/span> <span class=\"n\">target_log<\/span> <span class=\"ow\">in<\/span> <span class=\"n\">targets<\/span><span class=\"p\">:<\/span>\n    <span class=\"k\">for<\/span> <span class=\"n\">baseline_log<\/span> <span class=\"ow\">in<\/span> <span class=\"n\">baselines<\/span><span class=\"p\">:<\/span>\n        <span class=\"k\">if<\/span> <span class=\"n\">difference<\/span><span class=\"p\">(<\/span><span class=\"n\">target_log<\/span><span class=\"p\">,<\/span> <span class=\"n\">baseline_log<\/span><span class=\"p\">)<\/span> <span class=\"o\">&gt;<\/span> <span class=\"n\">threshold<\/span><span class=\"p\">:<\/span>\n            <span class=\"nb\">print<\/span><span class=\"p\">(<\/span><span class=\"n\">target_log<\/span><span class=\"p\">)<\/span>\n            <span class=\"k\">break<\/span>\n<\/pre><\/div>\n<div class=\"admonition note\">\n<p class=\"first admonition-title\">Note<\/p>\n<p class=\"last\">The log line order is not considered because it is often not deterministic.<\/p>\n<\/div>\n<p>The difference test can be implemented using <a class=\"reference external\" href=\"https:\/\/en.wikipedia.org\/wiki\/Levenshtein_distance\">Levenshtein distance<\/a> so that\nsmall variations in the logs can be ignored. Unfortunately, this solution is not efficient.\nAssuming it takes 20\u00b5sec to compare two lines, then processing 512 targets with 20_000 baselines would take more than 3 minutes.<\/p>\n<p>The next sections introduce a technique to improve the performance by converting the log lines into numerical vectors.<\/p>\n<\/div>\n<div class=\"section\" id=\"the-hashing-trick\">\n<h2>The Hashing Trick<\/h2>\n<p>Instead of working with the raw text, the log lines can be converted into numerical vectors using <a class=\"reference external\" href=\"https:\/\/en.wikipedia.org\/wiki\/Feature_hashing\">the hashing trick<\/a>.\nThe <a class=\"reference external\" href=\"https:\/\/scikit-learn.org\/\">scikit-learn<\/a> library provides such technique with the HashingVectorizer object:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"o\">&gt;&gt;&gt;<\/span> <span class=\"kn\">from<\/span> <span class=\"nn\">sklearn.feature_extraction.text<\/span> <span class=\"kn\">import<\/span> <span class=\"n\">HashingVectorizer<\/span>\n<span class=\"o\">&gt;&gt;&gt;<\/span> <span class=\"n\">vectorizer<\/span> <span class=\"o\">=<\/span> <span class=\"n\">HashingVectorizer<\/span><span class=\"p\">()<\/span>\n<span class=\"o\">&gt;&gt;&gt;<\/span> <span class=\"n\">baselines<\/span> <span class=\"o\">=<\/span> <span class=\"n\">vectorizer<\/span><span class=\"o\">.<\/span><span class=\"n\">fit_transform<\/span><span class=\"p\">([<\/span><span class=\"s2\">&quot;log line content&quot;<\/span><span class=\"p\">])<\/span>\n<span class=\"o\">&lt;<\/span><span class=\"mi\">1<\/span><span class=\"n\">x1048576<\/span> <span class=\"n\">sparse<\/span> <span class=\"n\">matrix<\/span> <span class=\"n\">of<\/span> <span class=\"nb\">type<\/span> <span class=\"s1\">&#39;&lt;class &#39;<\/span><span class=\"n\">numpy<\/span><span class=\"o\">.<\/span><span class=\"n\">float64<\/span><span class=\"s1\">&#39;&gt;&#39;<\/span>\n    <span class=\"k\">with<\/span> <span class=\"mi\">3<\/span> <span class=\"n\">stored<\/span> <span class=\"n\">elements<\/span> <span class=\"ow\">in<\/span> <span class=\"n\">Compressed<\/span> <span class=\"n\">Sparse<\/span> <span class=\"n\">Row<\/span> <span class=\"nb\">format<\/span><span class=\"o\">&gt;<\/span>\n<span class=\"o\">&gt;&gt;&gt;<\/span> <span class=\"p\">(<\/span><span class=\"n\">baselines<\/span><span class=\"o\">.<\/span><span class=\"n\">indices<\/span><span class=\"p\">,<\/span> <span class=\"n\">baselines<\/span><span class=\"o\">.<\/span><span class=\"n\">data<\/span><span class=\"p\">)<\/span>\n<span class=\"p\">(<\/span><span class=\"n\">array<\/span><span class=\"p\">([<\/span><span class=\"mi\">325140<\/span><span class=\"p\">,<\/span> <span class=\"mi\">377854<\/span><span class=\"p\">,<\/span> <span class=\"mi\">846328<\/span><span class=\"p\">],<\/span> <span class=\"n\">dtype<\/span><span class=\"o\">=<\/span><span class=\"n\">int32<\/span><span class=\"p\">),<\/span> <span class=\"n\">array<\/span><span class=\"p\">([<\/span><span class=\"mf\">0.57735027<\/span><span class=\"p\">,<\/span> <span class=\"mf\">0.57735027<\/span><span class=\"p\">,<\/span> <span class=\"mf\">0.57735027<\/span><span class=\"p\">]))<\/span>\n<\/pre><\/div>\n<p>As you can see, the result is a sparse vector of about 1 million columns where the indices are the word's hash value modulo the number of features.\nThe vector is defined with the Compressed Sparse Row (CSR) format, and fortunately there is an existing Rust library named <a class=\"reference external\" href=\"https:\/\/docs.rs\/sprs\">sprs<\/a> which provides\nan equivalent implementation. Here is how this vectorizer can be implemented:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"k\">use<\/span><span class=\"w\"> <\/span><span class=\"n\">sprs<\/span><span class=\"p\">::<\/span><span class=\"o\">*<\/span><span class=\"p\">;<\/span>\n<span class=\"k\">use<\/span><span class=\"w\"> <\/span><span class=\"n\">itertools<\/span><span class=\"p\">::<\/span><span class=\"n\">Itertools<\/span><span class=\"p\">;<\/span>\n<span class=\"k\">use<\/span><span class=\"w\"> <\/span><span class=\"n\">fxhash<\/span><span class=\"p\">::<\/span><span class=\"n\">hash32<\/span><span class=\"p\">;<\/span>\n\n<span class=\"sd\">\/\/\/ A type alias for sprs vector<\/span>\n<span class=\"k\">type<\/span><span class=\"w\"> <\/span><span class=\"nc\">SparseVec<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">CsVecBase<\/span><span class=\"o\">&lt;<\/span><span class=\"nb\">Vec<\/span><span class=\"o\">&lt;<\/span><span class=\"kt\">usize<\/span><span class=\"o\">&gt;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nb\">Vec<\/span><span class=\"o\">&lt;<\/span><span class=\"kt\">f64<\/span><span class=\"o\">&gt;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"kt\">f64<\/span><span class=\"o\">&gt;<\/span><span class=\"p\">;<\/span>\n<span class=\"k\">const<\/span><span class=\"w\"> <\/span><span class=\"n\">SIZE<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"kt\">usize<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"mi\">260000<\/span><span class=\"p\">;<\/span>\n\n<span class=\"sd\">\/\/\/ Word based hashing vectorizer<\/span>\n<span class=\"k\">pub<\/span><span class=\"w\"> <\/span><span class=\"k\">fn<\/span><span class=\"w\"> <\/span><span class=\"nf\">vectorize<\/span><span class=\"p\">(<\/span><span class=\"n\">line<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"kp\">&amp;<\/span><span class=\"kt\">str<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"nc\">SparseVec<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">keys<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">values<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">line<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">.<\/span><span class=\"n\">split<\/span><span class=\"p\">(<\/span><span class=\"sc\">&#39; &#39;<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">.<\/span><span class=\"n\">map<\/span><span class=\"p\">(<\/span><span class=\"o\">|<\/span><span class=\"n\">word<\/span><span class=\"o\">|<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">            <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">hash<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">hash32<\/span><span class=\"p\">(<\/span><span class=\"n\">word<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">            <\/span><span class=\"c1\">\/\/ alternate sign<\/span>\n<span class=\"w\">            <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">sign<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"n\">hash<\/span><span class=\"w\"> <\/span><span class=\"o\">&gt;=<\/span><span class=\"w\"> <\/span><span class=\"mi\">2147483648<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"> <\/span><span class=\"mf\">1.0<\/span><span class=\"w\"> <\/span><span class=\"p\">}<\/span><span class=\"w\"> <\/span><span class=\"k\">else<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"> <\/span><span class=\"o\">-<\/span><span class=\"mf\">1.0<\/span><span class=\"w\"> <\/span><span class=\"p\">};<\/span>\n<span class=\"w\">            <\/span><span class=\"p\">((<\/span><span class=\"n\">hash<\/span><span class=\"w\"> <\/span><span class=\"k\">as<\/span><span class=\"w\"> <\/span><span class=\"kt\">usize<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"o\">%<\/span><span class=\"w\"> <\/span><span class=\"n\">SIZE<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">sign<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">})<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">.<\/span><span class=\"n\">sorted_by<\/span><span class=\"p\">(<\/span><span class=\"o\">|<\/span><span class=\"n\">a<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">b<\/span><span class=\"o\">|<\/span><span class=\"w\"> <\/span><span class=\"nb\">Ord<\/span><span class=\"p\">::<\/span><span class=\"n\">cmp<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"n\">a<\/span><span class=\"p\">.<\/span><span class=\"mi\">0<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"n\">b<\/span><span class=\"p\">.<\/span><span class=\"mi\">0<\/span><span class=\"p\">))<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">.<\/span><span class=\"n\">dedup_by<\/span><span class=\"p\">(<\/span><span class=\"o\">|<\/span><span class=\"n\">a<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">b<\/span><span class=\"o\">|<\/span><span class=\"w\"> <\/span><span class=\"n\">a<\/span><span class=\"p\">.<\/span><span class=\"mi\">0<\/span><span class=\"w\"> <\/span><span class=\"o\">==<\/span><span class=\"w\"> <\/span><span class=\"n\">b<\/span><span class=\"p\">.<\/span><span class=\"mi\">0<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">.<\/span><span class=\"n\">unzip<\/span><span class=\"p\">();<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">CsVec<\/span><span class=\"p\">::<\/span><span class=\"n\">new<\/span><span class=\"p\">(<\/span><span class=\"n\">SIZE<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">keys<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">values<\/span><span class=\"p\">)<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<div class=\"admonition note\">\n<p class=\"first admonition-title\">Note<\/p>\n<p class=\"last\">Word order is not considered when using this trick.<\/p>\n<\/div>\n<p>The next section introduces how to compare such numerical vectors.<\/p>\n<\/div>\n<div class=\"section\" id=\"cosine-similarity\">\n<h2>Cosine Similarity<\/h2>\n<p>In data analysis, the <a class=\"reference external\" href=\"https:\/\/en.wikipedia.org\/wiki\/Cosine_similarity\">cosine similarity<\/a> is a measure of similarity between two sequences of numbers.\nBy applying the text book formula, the following function returns a number between 0 and 1, where 1 means\nthe vectors are similar, and 0 means they are different.<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"k\">pub<\/span><span class=\"w\"> <\/span><span class=\"k\">fn<\/span><span class=\"w\"> <\/span><span class=\"nf\">cosine_similarity<\/span><span class=\"p\">(<\/span><span class=\"n\">a<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"kp\">&amp;<\/span><span class=\"nc\">SparseVec<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">b<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"kp\">&amp;<\/span><span class=\"nc\">SparseVec<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">f64<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">a<\/span><span class=\"p\">.<\/span><span class=\"n\">dot<\/span><span class=\"p\">(<\/span><span class=\"n\">b<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"o\">\/<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">a<\/span><span class=\"p\">.<\/span><span class=\"n\">l2_norm<\/span><span class=\"p\">()<\/span><span class=\"w\"> <\/span><span class=\"o\">*<\/span><span class=\"w\"> <\/span><span class=\"n\">b<\/span><span class=\"p\">.<\/span><span class=\"n\">l2_norm<\/span><span class=\"p\">())<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<p>This measure works well with sparse vectors because only the non zero values are used.\nEven though this code performs almost as fast as the current logreduce's implementation,\nit is inefficient because the lines are still compared one by one.<\/p>\n<p>The next section introduces how to compute the cosine similarity between two lists of vectors using matrices.<\/p>\n<\/div>\n<div class=\"section\" id=\"pairwise-distance\">\n<h2>Pairwise Distance<\/h2>\n<p>The usual nearest neighbors algorithms do not work with sparse vectors.\nEven though the goal is to find the nearest neighbors,\nthe <a class=\"reference external\" href=\"https:\/\/scikit-learn.org\/\">scikit-learn<\/a> model uses a bruteforce algorithm when working with sparse data:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"o\">&gt;&gt;&gt;<\/span> <span class=\"kn\">from<\/span> <span class=\"nn\">sklearn.metrics.pairwise<\/span> <span class=\"kn\">import<\/span> <span class=\"n\">cosine_distances<\/span>\n<span class=\"o\">&gt;&gt;&gt;<\/span> <span class=\"n\">targets<\/span> <span class=\"o\">=<\/span> <span class=\"n\">vectorizer<\/span><span class=\"o\">.<\/span><span class=\"n\">fit_transform<\/span><span class=\"p\">([<\/span><span class=\"s2\">&quot;another line content&quot;<\/span><span class=\"p\">,<\/span> <span class=\"s2\">&quot;a traceback&quot;<\/span><span class=\"p\">])<\/span>\n<span class=\"o\">&gt;&gt;&gt;<\/span> <span class=\"n\">cosine_distances<\/span><span class=\"p\">(<\/span><span class=\"n\">baselines<\/span><span class=\"p\">,<\/span> <span class=\"n\">targets<\/span><span class=\"p\">)<\/span>\n<span class=\"n\">array<\/span><span class=\"p\">([[<\/span><span class=\"mf\">0.33333333<\/span><span class=\"p\">,<\/span> <span class=\"mf\">1.<\/span>        <span class=\"p\">]])<\/span>\n<\/pre><\/div>\n<p>As you can see, the result is a list of distances between the baselines and the targets.\n0.33 indicates that the first target is near the baseline, and the second target is the farthest: its distance is 1.\nThis technique is very fast because it leverages an optimized matrix multiplication operation.\nHere is how this function can be implemented:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"k\">pub<\/span><span class=\"w\"> <\/span><span class=\"k\">type<\/span><span class=\"w\"> <\/span><span class=\"nc\">FeaturesMatrix<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">CsMatBase<\/span><span class=\"o\">&lt;<\/span><span class=\"kt\">f64<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"kt\">usize<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nb\">Vec<\/span><span class=\"o\">&lt;<\/span><span class=\"kt\">usize<\/span><span class=\"o\">&gt;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nb\">Vec<\/span><span class=\"o\">&lt;<\/span><span class=\"kt\">usize<\/span><span class=\"o\">&gt;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"nb\">Vec<\/span><span class=\"o\">&lt;<\/span><span class=\"kt\">f64<\/span><span class=\"o\">&gt;&gt;<\/span><span class=\"p\">;<\/span>\n\n<span class=\"sd\">\/\/\/ Create a normalized matrix<\/span>\n<span class=\"k\">pub<\/span><span class=\"w\"> <\/span><span class=\"k\">fn<\/span><span class=\"w\"> <\/span><span class=\"nf\">create_mat<\/span><span class=\"p\">(<\/span><span class=\"n\">vectors<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"kp\">&amp;<\/span><span class=\"p\">[<\/span><span class=\"n\">SparseVec<\/span><span class=\"p\">])<\/span><span class=\"w\"> <\/span><span class=\"p\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"nc\">FeaturesMatrix<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"k\">mut<\/span><span class=\"w\"> <\/span><span class=\"n\">mat<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">TriMat<\/span><span class=\"p\">::<\/span><span class=\"n\">new<\/span><span class=\"p\">((<\/span><span class=\"n\">vectors<\/span><span class=\"p\">.<\/span><span class=\"n\">len<\/span><span class=\"p\">(),<\/span><span class=\"w\"> <\/span><span class=\"n\">SIZE<\/span><span class=\"p\">));<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">for<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">row<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">vector<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"k\">in<\/span><span class=\"w\"> <\/span><span class=\"n\">vectors<\/span><span class=\"p\">.<\/span><span class=\"n\">iter<\/span><span class=\"p\">().<\/span><span class=\"n\">enumerate<\/span><span class=\"p\">()<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">l2_norm<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">vector<\/span><span class=\"p\">.<\/span><span class=\"n\">l2_norm<\/span><span class=\"p\">();<\/span>\n<span class=\"w\">        <\/span><span class=\"k\">for<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">col<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">val<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"k\">in<\/span><span class=\"w\"> <\/span><span class=\"n\">vector<\/span><span class=\"p\">.<\/span><span class=\"n\">iter<\/span><span class=\"p\">()<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">            <\/span><span class=\"n\">mat<\/span><span class=\"p\">.<\/span><span class=\"n\">add_triplet<\/span><span class=\"p\">(<\/span><span class=\"n\">row<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">col<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"o\">*<\/span><span class=\"n\">val<\/span><span class=\"w\"> <\/span><span class=\"o\">\/<\/span><span class=\"w\"> <\/span><span class=\"n\">l2_norm<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">mat<\/span><span class=\"p\">.<\/span><span class=\"n\">to_csr<\/span><span class=\"p\">()<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"sd\">\/\/\/ Compute the smallest cosine distance between two normalized matrix. The rhs must be transposed.<\/span>\n<span class=\"k\">pub<\/span><span class=\"w\"> <\/span><span class=\"k\">fn<\/span><span class=\"w\"> <\/span><span class=\"nf\">search<\/span><span class=\"p\">(<\/span><span class=\"n\">baselines<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"kp\">&amp;<\/span><span class=\"nc\">FeaturesMatrix<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">targets<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"kp\">&amp;<\/span><span class=\"nc\">FeaturesMatrix<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"nb\">Vec<\/span><span class=\"o\">&lt;<\/span><span class=\"kt\">f64<\/span><span class=\"o\">&gt;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"k\">mut<\/span><span class=\"w\"> <\/span><span class=\"n\">result<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"fm\">vec!<\/span><span class=\"p\">[<\/span><span class=\"mf\">1.0<\/span><span class=\"p\">;<\/span><span class=\"w\"> <\/span><span class=\"n\">targets<\/span><span class=\"p\">.<\/span><span class=\"n\">cols<\/span><span class=\"p\">()];<\/span>\n<span class=\"w\">    <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"n\">distances_mat<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">baselines<\/span><span class=\"w\"> <\/span><span class=\"o\">*<\/span><span class=\"w\"> <\/span><span class=\"n\">targets<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">distances_mat<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">.<\/span><span class=\"n\">iter<\/span><span class=\"p\">()<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">.<\/span><span class=\"n\">for_each<\/span><span class=\"p\">(<\/span><span class=\"o\">|<\/span><span class=\"p\">(<\/span><span class=\"n\">v<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">_<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">col<\/span><span class=\"p\">))<\/span><span class=\"o\">|<\/span><span class=\"w\"> <\/span><span class=\"n\">result<\/span><span class=\"p\">[<\/span><span class=\"n\">col<\/span><span class=\"p\">]<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"mf\">1.0<\/span><span class=\"w\"> <\/span><span class=\"o\">-<\/span><span class=\"w\"> <\/span><span class=\"n\">v<\/span><span class=\"p\">).<\/span><span class=\"n\">min<\/span><span class=\"p\">(<\/span><span class=\"n\">result<\/span><span class=\"p\">[<\/span><span class=\"n\">col<\/span><span class=\"p\">]));<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">result<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<p>The trick is to perform the l2 normalizations before computing the cross product of the two matrices.\nThis yields a new matrix that contains the distances between each row.<\/p>\n<p>The <a class=\"reference external\" href=\"https:\/\/github.com\/logreduce\/logreduce-rust\/blob\/main\/python\/benches\/bench-index.py\">benchmark<\/a> shows that this new implementation performs almost four times faster, even with the overhead of converting Python and Rust types.\nMore importantly, running the full toolchain confirmed it produces the exact same results, the math worked, and that was a big relief!<\/p>\n<\/div>\n<div class=\"section\" id=\"conclusion\">\n<h2>Conclusion<\/h2>\n<p>Thanks to the <a class=\"reference external\" href=\"https:\/\/docs.rs\/sprs\">sprs<\/a> library, I was able to implement all the <a class=\"reference external\" href=\"https:\/\/scikit-learn.org\/\">scikit-learn<\/a> features used in logreduce.\nI wanted to use a higher level library such as <a class=\"reference external\" href=\"https:\/\/rust-ml.github.io\/linfa\/\">linfa<\/a>, but as suggested in this <a class=\"reference external\" href=\"https:\/\/github.com\/rust-ml\/linfa\/issues\/200\">issue<\/a>, the implementation is so simple that it can easily be done from scratch.<\/p>\n<p>This new code is simpler and more portable, and it's great to see Rust out-performing Python.\nPerhaps it is possible to use a more efficient algorithm with dense vectors.\nFor now I am satisfied with the current result.\nYou can find the complete code in the index library of <a class=\"reference external\" href=\"https:\/\/github.com\/logreduce\/logreduce-rust\">logreduce-rust<\/a><\/p>\n<p>It seems like the next step is to implement a log files iterator and build the html report.\nThat way the new implementation could be used standalone.<\/p>\n<p>I always welcome feedback, and if you would like to contribute, please join the <a class=\"reference external\" href=\"https:\/\/matrix.to\/#\/#logreduce:matrix.org\">#logreduce:matrix.org<\/a> chat room.<\/p>\n<p>Thank you for reading!<\/p>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2021 Jan 28 to Feb 16 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2021-jan-28-to-feb-16-summary.html","rel":"alternate"}},"published":"2022-02-16T10:00:00+00:00","updated":"2022-02-16T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2022-02-16:\/sprint-2021-jan-28-to-feb-16-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We added rust roles to zuul-jobs: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/q\/topic:zuul-jobs-rust\">https:\/\/review.opendev.org\/q\/topic:zuul-jobs-rust<\/a><\/li>\n<li>We started monitoring logscraper host<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We updated zuul and nodepool versions on sf-master to 5.0.0 on ubi-8 images<\/li>\n<li>We created SF \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We added rust roles to zuul-jobs: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/q\/topic:zuul-jobs-rust\">https:\/\/review.opendev.org\/q\/topic:zuul-jobs-rust<\/a><\/li>\n<li>We started monitoring logscraper host<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We updated zuul and nodepool versions on sf-master to 5.0.0 on ubi-8 images<\/li>\n<li>We created SF.3.7 epic and we plan to release this version during the next sprint with zuul, nodepool, gerrit and elk services containerized<\/li>\n<li>We added rootless container support to k1s: <a class=\"reference external\" href=\"https:\/\/pagure.io\/software-factory\/k1s\/pull-request\/2\">https:\/\/pagure.io\/software-factory\/k1s\/pull-request\/2<\/a><\/li>\n<li>We've added support for external authenticators in Zuul, we're currently working on handling zuul acls via the config repository.<\/li>\n<li>We wrote and discussed a story about a new zuul-search service <a class=\"reference external\" href=\"https:\/\/issues.redhat.com\/browse\/RHOSZUUL-775\">https:\/\/issues.redhat.com\/browse\/RHOSZUUL-775<\/a><\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Improving logreduce with Rust","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/improving-logreduce-with-rust.html","rel":"alternate"}},"published":"2022-02-10T00:00:00+00:00","updated":"2022-02-10T00:00:00+00:00","author":{"name":"tristanC"},"id":"tag:www.softwarefactory-project.io,2022-02-10:\/improving-logreduce-with-rust.html","summary":"<p>This article introduces <a class=\"reference external\" href=\"https:\/\/github.com\/logreduce\/logreduce-tokenizer\">logreduce-tokenizer<\/a> which leverages the <a class=\"reference external\" href=\"https:\/\/www.rust-lang.org\/\">Rust<\/a> programing language\nto improve <a class=\"reference external\" href=\"https:\/\/github.com\/logreduce\/logreduce\">logreduce<\/a> performance and reporting capabilities.<\/p>\n<p>In this post you will learn:<\/p>\n<ul class=\"simple\">\n<li>What logreduce is.<\/li>\n<li>How it works.<\/li>\n<li>Why we need this new function.<\/li>\n<li>What the upcoming roadmap is.<\/li>\n<\/ul>\n<div class=\"section\" id=\"logreduce\">\n<h2>Logreduce<\/h2>\n<p>Logreduce is a command line tool that can extract \u2026<\/p><\/div>","content":"<p>This article introduces <a class=\"reference external\" href=\"https:\/\/github.com\/logreduce\/logreduce-tokenizer\">logreduce-tokenizer<\/a> which leverages the <a class=\"reference external\" href=\"https:\/\/www.rust-lang.org\/\">Rust<\/a> programing language\nto improve <a class=\"reference external\" href=\"https:\/\/github.com\/logreduce\/logreduce\">logreduce<\/a> performance and reporting capabilities.<\/p>\n<p>In this post you will learn:<\/p>\n<ul class=\"simple\">\n<li>What logreduce is.<\/li>\n<li>How it works.<\/li>\n<li>Why we need this new function.<\/li>\n<li>What the upcoming roadmap is.<\/li>\n<\/ul>\n<div class=\"section\" id=\"logreduce\">\n<h2>Logreduce<\/h2>\n<p>Logreduce is a command line tool that can extract information from log files.\nIt is designed to assist continuous integration build investigation by looking for new content in build outputs.\nYou can learn more about the project in this <a class=\"reference external\" href=\"https:\/\/opensource.com\/article\/18\/9\/quiet-log-noise-python-and-machine-learning\">blog post<\/a>.<\/p>\n<p>The tool can be used like this:<\/p>\n<div class=\"highlight\"><pre><span><\/span>$<span class=\"w\"> <\/span>logreduce<span class=\"w\"> <\/span>job<span class=\"w\"> <\/span>--zuul-web<span class=\"w\"> <\/span>https:\/\/review.rdoproject.org\/zuul\/api\/<span class=\"w\"> <\/span>https:\/\/logserver.rdoproject.org\/UID\n<\/pre><\/div>\n<p>When given a <a class=\"reference external\" href=\"https:\/\/zuul-ci.org\">Zuul<\/a> API url and a target build log url logreduce will:<\/p>\n<ul class=\"simple\">\n<li>Look for baseline in the Zuul builds API.<\/li>\n<li>Download baseline and target logs.<\/li>\n<li>Index the baseline logs using a nearest neighbor model.<\/li>\n<li>Compute the target logs line's distances from the baseline.<\/li>\n<\/ul>\n<p>This process works well for continuous integration where two builds should be almost identical,\nbesides the exception that caused target failure.<\/p>\n<p>The next section explains the indexing process.<\/p>\n<\/div>\n<div class=\"section\" id=\"python-regexp-tokenizer\">\n<h2>Python regexp tokenizer<\/h2>\n<p>The process to compute the log line distances is as follows:<\/p>\n<ul class=\"simple\">\n<li>Tokenize the log line, for example, by replacing any dates or numbers.<\/li>\n<li>Create a feature vector using the Hashing Vectorizer utility from the <a class=\"reference external\" href=\"https:\/\/scikit-learn.org\/stable\/\">scikit-learn<\/a> library.<\/li>\n<li>Index the baseline in a NearestNeighbor model.<\/li>\n<li>Search the nearest neighbors of the target to compute the distance.<\/li>\n<\/ul>\n<p>Logreduce is designed to run in the post phase of failing jobs and it must run in a timely fashion.\nThus, logreduce uses heavy tokenization so that the search can be performed with a minimal memory footprint.<\/p>\n<p>The version 0.6 of logreduce uses a series of regular expressions to progressively replace known patterns by fixed tokens.\nHere is a demo implementation:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"n\">http_re<\/span> <span class=\"o\">=<\/span> <span class=\"n\">re<\/span><span class=\"o\">.<\/span><span class=\"n\">compile<\/span><span class=\"p\">(<\/span><span class=\"s2\">&quot;http[^<\/span><span class=\"se\">\\b<\/span><span class=\"s2\">]+&quot;<\/span><span class=\"p\">)<\/span>\n<span class=\"n\">days_re<\/span> <span class=\"o\">=<\/span> <span class=\"n\">re<\/span><span class=\"o\">.<\/span><span class=\"n\">compile<\/span><span class=\"p\">(<\/span><span class=\"s2\">&quot;sunday|monday|tuesday|wednesday|thursday|friday|saturday&quot;<\/span><span class=\"p\">)<\/span>\n\n<span class=\"k\">def<\/span> <span class=\"nf\">tokenize<\/span><span class=\"p\">(<\/span><span class=\"n\">line<\/span><span class=\"p\">:<\/span> <span class=\"nb\">str<\/span><span class=\"p\">)<\/span> <span class=\"o\">-&gt;<\/span> <span class=\"nb\">str<\/span><span class=\"p\">:<\/span>\n    <span class=\"n\">line<\/span> <span class=\"o\">=<\/span> <span class=\"n\">http_re<\/span><span class=\"o\">.<\/span><span class=\"n\">sub<\/span><span class=\"p\">(<\/span><span class=\"n\">line<\/span><span class=\"p\">,<\/span> <span class=\"s2\">&quot;URL&quot;<\/span><span class=\"p\">)<\/span>\n    <span class=\"n\">line<\/span> <span class=\"o\">=<\/span> <span class=\"n\">days_re<\/span><span class=\"o\">.<\/span><span class=\"n\">sub<\/span><span class=\"p\">(<\/span><span class=\"n\">line<\/span><span class=\"p\">,<\/span> <span class=\"s2\">&quot;DAY&quot;<\/span><span class=\"p\">)<\/span>\n    <span class=\"k\">return<\/span> <span class=\"n\">line<\/span>\n<\/pre><\/div>\n<p>For example, a tripleo build can produce 2GB of logs for a total of 7.5 million events,\nand this Python based implementation takes about one hour to extract 1843 anomalies.<\/p>\n<p>I suspected this tokenizer process to be inefficient for the following reasons:<\/p>\n<ul class=\"simple\">\n<li>Each step traverses the whole line.<\/li>\n<li>The token replacement does not respect word boundaries.<\/li>\n<li>It is hard to update, as any modification affects the whole process.<\/li>\n<\/ul>\n<p>My previous attempts at improving this implementation resulted in worse performance, and\/or false negatives.\nA particularly difficult challenge is differentiating system paths with base64-encoded strings, for example,\nis <em>nn2RZ\/ocRcL5as2EHQES0b\/I12a2GjWub0OQAGDq8iL5o8P0\/ogEWrpZmoBC<\/em> a file system path?<\/p>\n<p>The next section shows a new tokenizer implementation.<\/p>\n<\/div>\n<div class=\"section\" id=\"rust-tokenizer\">\n<h2>Rust tokenizer<\/h2>\n<p>I have been investigating a new tokenizer implementation using Rust.\nThe goal is to be able to do more work in less time. In particular, Rust enables efficient string processing:<\/p>\n<ul class=\"simple\">\n<li>The new tokenizer processes each word separately.<\/li>\n<li>Then, it recursively applies the same rules to each component, e.g. elements separated by <em>\/<\/em>.<\/li>\n<li>It uses a single string builder to create the output.<\/li>\n<\/ul>\n<p>Here is a demo implementation:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"k\">fn<\/span><span class=\"w\"> <\/span><span class=\"nf\">parse_literal<\/span><span class=\"p\">(<\/span><span class=\"n\">word<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"kp\">&amp;<\/span><span class=\"kt\">str<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"nb\">Option<\/span><span class=\"o\">&lt;&amp;<\/span><span class=\"kt\">str<\/span><span class=\"o\">&gt;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"n\">is_date<\/span><span class=\"p\">(<\/span><span class=\"n\">word<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"nb\">Some<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;%DATE&quot;<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span><span class=\"w\"> <\/span><span class=\"k\">else<\/span><span class=\"w\"> <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"n\">is_url<\/span><span class=\"p\">(<\/span><span class=\"n\">word<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"nb\">Some<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;%URL&quot;<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span><span class=\"w\"> <\/span><span class=\"k\">else<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"nb\">None<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"k\">fn<\/span><span class=\"w\"> <\/span><span class=\"nf\">do_process<\/span><span class=\"p\">(<\/span><span class=\"n\">word<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"kp\">&amp;<\/span><span class=\"kt\">str<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">result<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"kp\">&amp;<\/span><span class=\"nc\">mut<\/span><span class=\"w\"> <\/span><span class=\"nb\">String<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">  <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"nb\">Some<\/span><span class=\"p\">(<\/span><span class=\"n\">token<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">parse_literal<\/span><span class=\"p\">(<\/span><span class=\"n\">word<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">     <\/span><span class=\"n\">result<\/span><span class=\"p\">.<\/span><span class=\"n\">push_str<\/span><span class=\"p\">(<\/span><span class=\"n\">token<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">  <\/span><span class=\"p\">}<\/span><span class=\"w\"> <\/span><span class=\"k\">else<\/span><span class=\"w\"> <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"nb\">Some<\/span><span class=\"p\">((<\/span><span class=\"n\">w1<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">w2<\/span><span class=\"p\">))<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">word<\/span><span class=\"p\">.<\/span><span class=\"n\">split_once<\/span><span class=\"p\">(<\/span><span class=\"sc\">&#39;\/&#39;<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">     <\/span><span class=\"n\">do_process<\/span><span class=\"p\">(<\/span><span class=\"n\">w1<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">result<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">     <\/span><span class=\"n\">do_process<\/span><span class=\"p\">(<\/span><span class=\"n\">w2<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">result<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">  <\/span><span class=\"p\">}<\/span><span class=\"w\"> <\/span><span class=\"k\">else<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">     <\/span><span class=\"n\">result<\/span><span class=\"p\">.<\/span><span class=\"n\">push_str<\/span><span class=\"p\">(<\/span><span class=\"n\">word<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">  <\/span><span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"k\">fn<\/span><span class=\"w\"> <\/span><span class=\"nf\">tokenize<\/span><span class=\"p\">(<\/span><span class=\"n\">line<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"kp\">&amp;<\/span><span class=\"kt\">str<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"nb\">String<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">  <\/span><span class=\"kd\">let<\/span><span class=\"w\"> <\/span><span class=\"k\">mut<\/span><span class=\"w\"> <\/span><span class=\"n\">result<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"nb\">String<\/span><span class=\"p\">::<\/span><span class=\"n\">with_capacity<\/span><span class=\"p\">(<\/span><span class=\"n\">line<\/span><span class=\"p\">.<\/span><span class=\"n\">len<\/span><span class=\"p\">());<\/span>\n<span class=\"w\">  <\/span><span class=\"k\">for<\/span><span class=\"w\"> <\/span><span class=\"n\">word<\/span><span class=\"w\"> <\/span><span class=\"k\">in<\/span><span class=\"w\"> <\/span><span class=\"n\">words<\/span><span class=\"p\">(<\/span><span class=\"n\">line<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">     <\/span><span class=\"n\">do_process<\/span><span class=\"p\">(<\/span><span class=\"n\">word<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"k\">mut<\/span><span class=\"w\"> <\/span><span class=\"n\">result<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">   <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">   <\/span><span class=\"n\">result<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<p>This kind of work is too expensive to do in a dynamic language such as Python.\nThis new Rust implementation is much faster while using more complex rules to provide better results:<\/p>\n<ul class=\"simple\">\n<li>Fewer false positives because the noise filter is more efficient.<\/li>\n<li>Fewer false negatives because the log semantic is better preserved.<\/li>\n<\/ul>\n<p>The result is really exciting. The tokenizer benchmark shows it performs 7.3 times faster.\nAnd running the full toolchain on the previous tripleo build now takes:\n605.86 seconds to extract 851 anomalies (out of 7.5 million events found in a 2GB build output).\nThe number of anomalies went down, mostly because more noise got filtered, but the report\nalso contains new valid anomalies that previously went unnoticed.<\/p>\n<\/div>\n<div class=\"section\" id=\"calling-rust-from-python\">\n<h2>Calling Rust from Python<\/h2>\n<p>The new tokenizer is integrated in the current code using <a class=\"reference external\" href=\"https:\/\/pyo3.rs\">PyO3<\/a>, which makes it\nvery easy to call Rust from Python. The whole binding is defined as:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"k\">use<\/span><span class=\"w\"> <\/span><span class=\"n\">pyo3<\/span><span class=\"p\">::<\/span><span class=\"n\">prelude<\/span><span class=\"p\">::<\/span><span class=\"o\">*<\/span><span class=\"p\">;<\/span>\n\n<span class=\"cp\">#[pyfunction]<\/span>\n<span class=\"k\">fn<\/span><span class=\"w\"> <\/span><span class=\"nf\">process<\/span><span class=\"p\">(<\/span><span class=\"n\">line<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"kp\">&amp;<\/span><span class=\"kt\">str<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"nb\">String<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">  <\/span><span class=\"n\">tokenize<\/span><span class=\"p\">(<\/span><span class=\"n\">line<\/span><span class=\"p\">)<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"cp\">#[pymodule]<\/span>\n<span class=\"k\">fn<\/span><span class=\"w\"> <\/span><span class=\"nf\">logreduce_tokenizer<\/span><span class=\"p\">(<\/span><span class=\"n\">_py<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"nc\">Python<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">m<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"kp\">&amp;<\/span><span class=\"nc\">PyModule<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"nc\">PyResult<\/span><span class=\"o\">&lt;<\/span><span class=\"p\">()<\/span><span class=\"o\">&gt;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">  <\/span><span class=\"n\">m<\/span><span class=\"p\">.<\/span><span class=\"n\">add_function<\/span><span class=\"p\">(<\/span><span class=\"n\">wrap_pyfunction<\/span><span class=\"o\">!<\/span><span class=\"p\">(<\/span><span class=\"n\">process<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">m<\/span><span class=\"p\">)<\/span><span class=\"o\">?<\/span><span class=\"p\">)<\/span><span class=\"o\">?<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">  <\/span><span class=\"nb\">Ok<\/span><span class=\"p\">(())<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<p>The final python module can be produced using <a class=\"reference external\" href=\"https:\/\/setuptools-rust.readthedocs.io\/en\/latest\/\">setuptools-rust<\/a> with this setup:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"c1\"># setup.py<\/span>\n<span class=\"kn\">from<\/span> <span class=\"nn\">setuptools<\/span> <span class=\"kn\">import<\/span> <span class=\"n\">setup<\/span>\n<span class=\"kn\">from<\/span> <span class=\"nn\">setuptools_rust<\/span> <span class=\"kn\">import<\/span> <span class=\"n\">Binding<\/span><span class=\"p\">,<\/span> <span class=\"n\">RustExtension<\/span>\n\n<span class=\"n\">setup<\/span><span class=\"p\">(<\/span>\n    <span class=\"n\">name<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;logreduce-tokenizer&quot;<\/span><span class=\"p\">,<\/span>\n    <span class=\"n\">version<\/span><span class=\"o\">=<\/span><span class=\"s2\">&quot;1.0&quot;<\/span><span class=\"p\">,<\/span>\n    <span class=\"n\">rust_extensions<\/span><span class=\"o\">=<\/span><span class=\"p\">[<\/span><span class=\"n\">RustExtension<\/span><span class=\"p\">(<\/span><span class=\"s2\">&quot;logreduce_tokenizer&quot;<\/span><span class=\"p\">,<\/span> <span class=\"n\">binding<\/span><span class=\"o\">=<\/span><span class=\"n\">Binding<\/span><span class=\"o\">.<\/span><span class=\"n\">PyO3<\/span><span class=\"p\">)],<\/span>\n    <span class=\"n\">zip_safe<\/span><span class=\"o\">=<\/span><span class=\"kc\">False<\/span><span class=\"p\">,<\/span>\n<span class=\"p\">)<\/span>\n<\/pre><\/div>\n<\/div>\n<div class=\"section\" id=\"a-new-roadmap-for-logreduce\">\n<h2>A new roadmap for logreduce<\/h2>\n<p>I would like to investigate if other parts of the toolchain can also benefit from a rewrite in Rust, in particular:<\/p>\n<ul class=\"simple\">\n<li>Implement the vectorizer in the tokenizer, perhaps by directly producing an unboxed numpy array.<\/li>\n<li>Replace scikit-learn with <a class=\"reference external\" href=\"https:\/\/horasearch.com\/\">hora<\/a>.<\/li>\n<li>Process the log file in parallel using the <a class=\"reference external\" href=\"https:\/\/docs.rs\/rayon\/latest\/rayon\/\">rayon<\/a> library.<\/li>\n<li>Skip unicode decoding, by manually replacing non ascii codepoints into fixed tokens. That should provide a significant performance boost.<\/li>\n<\/ul>\n<p>At that point, it might be worth migrating the remaining parts, such as the html renderer.\nThe main reasons to replace Python with Rust are:<\/p>\n<ul class=\"simple\">\n<li><a class=\"reference external\" href=\"https:\/\/doc.rust-lang.org\/book\/ch06-00-enums.html\">Algebraic Data Types<\/a>, this is the most important feature as it can be used to represent the data model in a concise and transparent way. This is particularly useful when modifying the code.<\/li>\n<li>Performance, where critical parts can leverage hardware optimisation such as SIMD.<\/li>\n<li>Distribution, where the program can be delivered as a ready to use binary, which can be easily embedded in CI jobs.<\/li>\n<li>The cargo toolchain, to manage dependencies and run doctest without a fuss.<\/li>\n<\/ul>\n<p>I always welcome feedback, and if you would like to contribute, please join the <a class=\"reference external\" href=\"https:\/\/matrix.to\/#\/#logreduce:matrix.org\">#logreduce:matrix.org<\/a> chat room.<\/p>\n<p>Thank you for reading!<\/p>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2021 Jan 07 to Jan 26 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2021-jan-07-to-jan-26-summary.html","rel":"alternate"}},"published":"2022-01-26T10:00:00+00:00","updated":"2022-01-26T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2022-01-26:\/sprint-2021-jan-07-to-jan-26-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We finally fix an issue with Reed in Opensearch Opendev instance that index pattern was not visible<\/li>\n<li>We reviewed the IBM Cloud VPC driver for nodepool<\/li>\n<li>We created a visualisation of the zuul git history with gource for \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We finally fix an issue with Reed in Opensearch Opendev instance that index pattern was not visible<\/li>\n<li>We reviewed the IBM Cloud VPC driver for nodepool<\/li>\n<li>We created a visualisation of the zuul git history with gource for the 10 years anniversary: <a class=\"reference external\" href=\"https:\/\/www.youtube.com\/watch?v=0gLONkPZ1a0\">https:\/\/www.youtube.com\/watch?v=0gLONkPZ1a0<\/a><\/li>\n<li>We added a new ChangeReady event to the gerritbot-matrix: <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/gerritbot-matrix\/+\/23646\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/gerritbot-matrix\/+\/23646<\/a> and deployed the new image: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/opendev\/system-config\/+\/825131\">https:\/\/review.opendev.org\/c\/opendev\/system-config\/+\/825131<\/a><\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We created ubi containers for nodepool (4.3.0) and zuul (4.11.0 with patches, then 4.12.0) but CI fails related to fqdn_update tasks<\/li>\n<li>We've worked on several mechanisms to add patches to our containers 1\/ at build time 2\/ semi live-patching on a deployment<\/li>\n<li>The zuul web UI is now installed from the zuul-web container instead of using an RPM package. This ensures the GUI is always up to date with the current version of zuul-web.<\/li>\n<li>We helped some folks on the public Matrix channel <a class=\"reference external\" href=\"https:\/\/app.element.io\/#\/room\">https:\/\/app.element.io\/#\/room<\/a>\/#softwarefactory-project:matrix.org<\/li>\n<li>We integrated the gerrit version 3.4: <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/q\/topic:%22gerrit-3.4-master%22\">https:\/\/softwarefactory-project.io\/r\/q\/topic:%22gerrit-3.4-master%22<\/a><\/li>\n<li>We deleted the taiga project<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2021 Dec 17 to Jan 05 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2021-dec-17-to-jan-05-summary.html","rel":"alternate"}},"published":"2022-01-05T10:00:00+00:00","updated":"2022-01-05T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2022-01-05:\/sprint-2021-dec-17-to-jan-05-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We proposed a patch for Zuul that adds only those events to the zookeeper queue that will be later processed by zuul <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/822484\">https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/822484<\/a><\/li>\n<li>Pagination was merged in zuul web ui before \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We proposed a patch for Zuul that adds only those events to the zookeeper queue that will be later processed by zuul <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/822484\">https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/822484<\/a><\/li>\n<li>Pagination was merged in zuul web ui before the holidays<\/li>\n<li>We got access to ibmcloud for reviewing a new nodepool driver<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We move Opendistro to Opensearch that will be running as container services<\/li>\n<li>We're working on improving keycloak integration - we will also support plugging external openidconnect IdPs to zuul, allowing for example using Red Hat SSO downstream even if keycloak integration isn't complete in SF.<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2021 Nov 26 to Dec 15 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2021-nov-26-to-dec-15-summary.html","rel":"alternate"}},"published":"2021-12-15T10:00:00+00:00","updated":"2021-12-15T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2021-12-15:\/sprint-2021-nov-26-to-dec-15-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We worked on pagination support for zuul search pages (builds &amp; buildsets) - <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/q\/topic:%22web_results_pagination%22+(status:open%20OR%20status:merged\">https:\/\/review.opendev.org\/q\/topic:%22web_results_pagination%22+(status:open%20OR%20status:merged<\/a>)<\/li>\n<li>All the web UI admin changes were merged<\/li>\n<li>We added support for arbitrary user \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We worked on pagination support for zuul search pages (builds &amp; buildsets) - <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/q\/topic:%22web_results_pagination%22+(status:open%20OR%20status:merged\">https:\/\/review.opendev.org\/q\/topic:%22web_results_pagination%22+(status:open%20OR%20status:merged<\/a>)<\/li>\n<li>All the web UI admin changes were merged<\/li>\n<li>We added support for arbitrary user to the gerritbot-matrix image<\/li>\n<li>We configured logscraper01.openstack.org to push openstack logs to testing Opensearch instance<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We started working on upgrading gerrit to version 3.4<\/li>\n<li>We published managesf on pypi for nodepool-ubi containers. We have also to publish python-sfmanager but it fails right now<\/li>\n<li>We are working to avoid to use update-ca-trust on zuul containers but instead use the bundle.crt from the host &lt;- is that the same as the CA pb encountered on gerrit containers ? (yes, it's to prepare that)<\/li>\n<li>We are implementing support of keycloak in config-update, so that groups are synchronized with the resources in the config repo - maybe demo<\/li>\n<li>We move logstash and curator from package base deployment to containers<\/li>\n<li>We almost finish moving Opendistro (kibana, elasticsearch) to opensearch and opensearch dashboards base on the container service<\/li>\n<li>We create a python-builder container image, but after discussion we should create service container image simplier<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Mitigate CVE-2021-45046","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/mitigate-cve-2021-45046.html","rel":"alternate"}},"published":"2021-12-15T00:00:00+00:00","updated":"2021-12-15T00:00:00+00:00","author":{"name":"sf"},"id":"tag:www.softwarefactory-project.io,2021-12-15:\/mitigate-cve-2021-45046.html","summary":"<p>As a followup on the log4j recent vulnerability a notice has been made about that the previous mitigation\nmight not be enough (<a class=\"reference external\" href=\"https:\/\/logging.apache.org\/log4j\/2.x\/security.html\">https:\/\/logging.apache.org\/log4j\/2.x\/security.html<\/a>).<\/p>\n<p>The vulnerability is affecting the following Software Factory services:<\/p>\n<ul class=\"simple\">\n<li>elasticsearch<\/li>\n<li>logstash<\/li>\n<\/ul>\n<p>Install the mitigation from the install server by \u2026<\/p>","content":"<p>As a followup on the log4j recent vulnerability a notice has been made about that the previous mitigation\nmight not be enough (<a class=\"reference external\" href=\"https:\/\/logging.apache.org\/log4j\/2.x\/security.html\">https:\/\/logging.apache.org\/log4j\/2.x\/security.html<\/a>).<\/p>\n<p>The vulnerability is affecting the following Software Factory services:<\/p>\n<ul class=\"simple\">\n<li>elasticsearch<\/li>\n<li>logstash<\/li>\n<\/ul>\n<p>Install the mitigation from the install server by running these commands:<\/p>\n<div class=\"highlight\"><pre><span><\/span>ansible<span class=\"w\"> <\/span>elasticsearch,logstash<span class=\"w\"> <\/span>--become<span class=\"w\"> <\/span>-m<span class=\"w\"> <\/span>yum<span class=\"w\"> <\/span>-a<span class=\"w\"> <\/span><span class=\"s2\">&quot;name=zip state=present&quot;<\/span>\n\nansible<span class=\"w\"> <\/span>elasticsearch<span class=\"w\"> <\/span>--become<span class=\"w\"> <\/span>-m<span class=\"w\"> <\/span>shell<span class=\"w\"> <\/span>-a<span class=\"w\"> <\/span><span class=\"s2\">&quot;cmd=&#39;zip -q -d \/usr\/share\/elasticsearch\/lib\/log4j-core-2.11.1.jar org\/apache\/logging\/log4j\/core\/lookup\/JndiLookup.class&#39;&quot;<\/span>\nansible<span class=\"w\"> <\/span>elasticsearch<span class=\"w\"> <\/span>--become<span class=\"w\"> <\/span>-m<span class=\"w\"> <\/span>shell<span class=\"w\"> <\/span>-a<span class=\"w\"> <\/span><span class=\"s2\">&quot;cmd=&#39;zip -q -d \/usr\/share\/elasticsearch\/plugins\/opendistro-performance-analyzer\/performance-analyzer-rca\/lib\/log4j-core-2.13.0.jar org\/apache\/logging\/log4j\/core\/lookup\/JndiLookup.class&#39;&quot;<\/span>\nansible<span class=\"w\"> <\/span>elasticsearch<span class=\"w\"> <\/span>--become<span class=\"w\"> <\/span>-m<span class=\"w\"> <\/span>shell<span class=\"w\"> <\/span>-a<span class=\"w\"> <\/span><span class=\"s2\">&quot;cmd=&#39;zip -q -d \/usr\/share\/elasticsearch\/performance-analyzer-rca\/lib\/log4j-core-2.13.0.jar org\/apache\/logging\/log4j\/core\/lookup\/JndiLookup.class&#39;&quot;<\/span>\nansible<span class=\"w\"> <\/span>elasticsearch<span class=\"w\"> <\/span>-m<span class=\"w\"> <\/span>service<span class=\"w\"> <\/span>-a<span class=\"w\"> <\/span><span class=\"s2\">&quot;name=elasticsearch state=restarted&quot;<\/span>\n\nansible<span class=\"w\"> <\/span>logstash<span class=\"w\"> <\/span>--become<span class=\"w\"> <\/span>-m<span class=\"w\"> <\/span>shell<span class=\"w\"> <\/span>-a<span class=\"w\"> <\/span><span class=\"s2\">&quot;cmd=&#39;zip -q -d \/usr\/share\/logstash\/logstash-core\/lib\/jars\/log4j-core-2.13.3.jar org\/apache\/logging\/log4j\/core\/lookup\/JndiLookup.class&#39;&quot;<\/span>\nansible<span class=\"w\"> <\/span>logstash<span class=\"w\"> <\/span>-m<span class=\"w\"> <\/span>service<span class=\"w\"> <\/span>-a<span class=\"w\"> <\/span><span class=\"s2\">&quot;name=logstash state=restarted&quot;<\/span>\n<\/pre><\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Mitigate CVE-2021-44228","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/mitigate-cve-2021-44228.html","rel":"alternate"}},"published":"2021-12-10T00:00:00+00:00","updated":"2021-12-10T00:00:00+00:00","author":{"name":"sf"},"id":"tag:www.softwarefactory-project.io,2021-12-10:\/mitigate-cve-2021-44228.html","summary":"<p>An important Java vulnerability is affecting the following Software Factory service:<\/p>\n<ul class=\"simple\">\n<li>elasticsearch<\/li>\n<li>logstash<\/li>\n<\/ul>\n<p>Install the mitigation from the install server by running these commands:<\/p>\n<div class=\"highlight\"><pre><span><\/span>ansible<span class=\"w\"> <\/span>elasticsearch<span class=\"w\"> <\/span>-m<span class=\"w\"> <\/span>lineinfile<span class=\"w\"> <\/span>-a<span class=\"w\"> <\/span><span class=\"s2\">&quot;path=\/etc\/sysconfig\/elasticsearch regexp=&#39;^ES_JAVA_OPTS=.*&#39; line=&#39;ES_JAVA_OPTS=\\&quot;-Dlog4j2.formatMsgNoLookups=true\\&quot;&#39;&quot;<\/span>\nansible<span class=\"w\"> <\/span>elasticsearch<span class=\"w\"> <\/span>-m<span class=\"w\"> <\/span>service<span class=\"w\">    <\/span>-a<span class=\"w\"> <\/span><span class=\"s2\">&quot;name=elasticsearch state=restarted&quot;<\/span>\n\nansible \u2026<\/pre><\/div>","content":"<p>An important Java vulnerability is affecting the following Software Factory service:<\/p>\n<ul class=\"simple\">\n<li>elasticsearch<\/li>\n<li>logstash<\/li>\n<\/ul>\n<p>Install the mitigation from the install server by running these commands:<\/p>\n<div class=\"highlight\"><pre><span><\/span>ansible<span class=\"w\"> <\/span>elasticsearch<span class=\"w\"> <\/span>-m<span class=\"w\"> <\/span>lineinfile<span class=\"w\"> <\/span>-a<span class=\"w\"> <\/span><span class=\"s2\">&quot;path=\/etc\/sysconfig\/elasticsearch regexp=&#39;^ES_JAVA_OPTS=.*&#39; line=&#39;ES_JAVA_OPTS=\\&quot;-Dlog4j2.formatMsgNoLookups=true\\&quot;&#39;&quot;<\/span>\nansible<span class=\"w\"> <\/span>elasticsearch<span class=\"w\"> <\/span>-m<span class=\"w\"> <\/span>service<span class=\"w\">    <\/span>-a<span class=\"w\"> <\/span><span class=\"s2\">&quot;name=elasticsearch state=restarted&quot;<\/span>\n\nansible<span class=\"w\"> <\/span>logstash<span class=\"w\">      <\/span>-m<span class=\"w\"> <\/span>lineinfile<span class=\"w\"> <\/span>-a<span class=\"w\"> <\/span><span class=\"s2\">&quot;path=\/etc\/sysconfig\/logstash regexp=&#39;^LS_JAVA_OPTS=.*&#39; line=&#39;LS_JAVA_OPTS=\\&quot;-Dlog4j2.formatMsgNoLookups=true\\&quot;&#39; create=yes&quot;<\/span>\nansible<span class=\"w\"> <\/span>logstash<span class=\"w\">      <\/span>-m<span class=\"w\"> <\/span>service<span class=\"w\">    <\/span>-a<span class=\"w\"> <\/span><span class=\"s2\">&quot;name=logstash state=restarted&quot;<\/span>\n<\/pre><\/div>\n<p>Note that Gerrit and ZooKeeper are not affected, see:<\/p>\n<ul class=\"simple\">\n<li><a class=\"reference external\" href=\"https:\/\/www.gerritcodereview.com\/2021-12-13-log4j-statement.html\">https:\/\/www.gerritcodereview.com\/2021-12-13-log4j-statement.html<\/a><\/li>\n<li><a class=\"reference external\" href=\"https:\/\/issues.apache.org\/jira\/browse\/ZOOKEEPER-4423\">https:\/\/issues.apache.org\/jira\/browse\/ZOOKEEPER-4423<\/a><\/li>\n<\/ul>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2021 Nov 05 to Nov 24 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2021-nov-05-to-nov-24-summary.html","rel":"alternate"}},"published":"2021-11-24T10:00:00+00:00","updated":"2021-11-24T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2021-11-24:\/sprint-2021-nov-05-to-nov-24-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We created a openstack\/ci-log-processing repository and we propose changes for new log processing workflow<\/li>\n<li>We've worked on adding partial text search in zuul: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/817949\">https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/817949<\/a> - this needs a GUI followup.<\/li>\n<li>We've \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We created a openstack\/ci-log-processing repository and we propose changes for new log processing workflow<\/li>\n<li>We've worked on adding partial text search in zuul: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/817949\">https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/817949<\/a> - this needs a GUI followup.<\/li>\n<li>We've progressed on the admin GUI to the point that the patch chain is ready for review: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/q\/topic:%22GUI_admin%22+(status:open%20OR%20status:merged\">https:\/\/review.opendev.org\/q\/topic:%22GUI_admin%22+(status:open%20OR%20status:merged<\/a>) - <a class=\"reference external\" href=\"https:\/\/zuuldemo.strangerpings.eu\">https:\/\/zuuldemo.strangerpings.eu<\/a> is a showcase<\/li>\n<li>We discussed the future of the zuul-runner and how it should be delayed until zuul v5<\/li>\n<li>We fixed issue in Gitlab crawler where A MR with an empty description cannot be proceseed<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We added functional tests for ansible-role-elastic-recheck<\/li>\n<li>We validated zuul-$service-ubi containers with sf-config and updated master branch to use them<\/li>\n<li>We started to create nodepool-$service-ubi containers for sf-config<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2021 Oct 14 to Nov 03 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2021-oct-14-to-nov-03-summary.html","rel":"alternate"}},"published":"2021-11-03T10:00:00+00:00","updated":"2021-11-03T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2021-11-03:\/sprint-2021-oct-14-to-nov-03-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>Work started with James Blair to progress on web admin GUI. Prerequisite patches to zuul-web got merged, and we are setting up a demo machine at <a class=\"reference external\" href=\"https:\/\/zuuldemo.strangerpings.eu\/zuul\">https:\/\/zuuldemo.strangerpings.eu\/zuul<\/a> based on the demo compose.<\/li>\n<li>we added \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>Work started with James Blair to progress on web admin GUI. Prerequisite patches to zuul-web got merged, and we are setting up a demo machine at <a class=\"reference external\" href=\"https:\/\/zuuldemo.strangerpings.eu\/zuul\">https:\/\/zuuldemo.strangerpings.eu\/zuul<\/a> based on the demo compose.<\/li>\n<li>we added support for podman in the demo compose.<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We created zuul containers based on Red Hat Universal Base Image 8<\/li>\n<li>We improved our ci, sf-tenant job now use multiple hosts for zuul services <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/cgit\/software-factory\/sf-ci\/tree\/roles\/configure-sfconfig\/templates\/multinode_arch.yaml.j2\">https:\/\/softwarefactory-project.io\/cgit\/software-factory\/sf-ci\/tree\/roles\/configure-sfconfig\/templates\/multinode_arch.yaml.j2<\/a><\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2021 Sep 24 to Oct 13 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2021-sep-24-to-oct-13-summary.html","rel":"alternate"}},"published":"2021-10-13T10:00:00+00:00","updated":"2021-10-13T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2021-10-13:\/sprint-2021-sep-24-to-oct-13-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We updated the gerritbot-matrix to include change numbers<\/li>\n<li>We added gantt-like charts to buildsets pages<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We removed zuul package depends from sf-config, it was used to avoid concurrency when zuul db was initialized and it's not \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We updated the gerritbot-matrix to include change numbers<\/li>\n<li>We added gantt-like charts to buildsets pages<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We removed zuul package depends from sf-config, it was used to avoid concurrency when zuul db was initialized and it's not needed anymore for this part is now done by zuul<\/li>\n<li>We improved templates used to generate script to initialize container<\/li>\n<li>We started to improve sf-tenant multinode job by adding dedicated zuul-scheduler, zuul-executor and zuul merger hosts<\/li>\n<li>We started to work to containerize keycloak service<\/li>\n<li>we fixed some tests related to keycloak integration, currently working on fixing kibana &lt;-&gt; integration<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2021 Sep 03 to Sep 22 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2021-sep-03-to-sep-22-summary.html","rel":"alternate"}},"published":"2021-09-22T10:00:00+00:00","updated":"2021-09-22T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2021-09-22:\/sprint-2021-sep-03-to-sep-22-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We proposed an etherpad backend for the statusbot: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/opendev\/statusbot\/+\/807946\/1\">https:\/\/review.opendev.org\/c\/opendev\/statusbot\/+\/807946\/1<\/a><\/li>\n<li>We got some changes merged on Zuul's GUI, mainly preliminary work for pagination support in searches<\/li>\n<li>We work on mirroring Opendev \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We proposed an etherpad backend for the statusbot: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/opendev\/statusbot\/+\/807946\/1\">https:\/\/review.opendev.org\/c\/opendev\/statusbot\/+\/807946\/1<\/a><\/li>\n<li>We got some changes merged on Zuul's GUI, mainly preliminary work for pagination support in searches<\/li>\n<li>We work on mirroring Opendev log output to our opensearch host<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We worked to contairized gerrit service<\/li>\n<li>We've started to containerize keycloak service<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2021 Aug 13 to Sep 01 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2021-aug-13-to-sep-01-summary.html","rel":"alternate"}},"published":"2021-09-01T10:00:00+00:00","updated":"2021-09-01T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2021-09-01:\/sprint-2021-aug-13-to-sep-01-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We've reworked the admin UI patches to remove dependency on redux-oidc, which is unmaintained. We know use react-oidc (based on oidc-client-js)<\/li>\n<li>We've reworked various patches and reviews<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We added 'ansible_setup_timeout' parameter for zuul-executor service, it's useful \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We've reworked the admin UI patches to remove dependency on redux-oidc, which is unmaintained. We know use react-oidc (based on oidc-client-js)<\/li>\n<li>We've reworked various patches and reviews<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We added 'ansible_setup_timeout' parameter for zuul-executor service, it's useful when executor are not on the same DC than nodepool instances <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-config\/+\/22551\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-config\/+\/22551<\/a><\/li>\n<li>We fixed sf-ssh populate_hosts play by using 'hostnameclt' instead 'hostname' <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-config\/+\/22548\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-config\/+\/22548<\/a><\/li>\n<li>We fixed nodepool-minimal element to be compatible with debian systems <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-elements\/+\/22550\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-elements\/+\/22550<\/a><\/li>\n<li>We started to work to containerized gerrit service but we have issue with upstream image with hardcoded volumes on the dockerfile (we can't bind mount directories nor change gerrit uid).<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2021 Jul 23 to Aug 11 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2021-jul-23-to-aug-11-summary.html","rel":"alternate"}},"published":"2021-08-11T10:00:00+00:00","updated":"2021-08-11T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2021-08-11:\/sprint-2021-jul-23-to-aug-11-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We deployed the new gerritbot service for the #test:opendev.org matrix room<\/li>\n<li>We investigated using prometheus for opendev infrastructure<\/li>\n<li>We discussed using React Hooks for the zuul web interface<\/li>\n<li>zuul web auth: we worked on UX flow \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We deployed the new gerritbot service for the #test:opendev.org matrix room<\/li>\n<li>We investigated using prometheus for opendev infrastructure<\/li>\n<li>We discussed using React Hooks for the zuul web interface<\/li>\n<li>zuul web auth: we worked on UX flow and design with felixedel,\ninvestigated an immutability problem with redux-oidc. Added some\nvideos to show what the changes look like:<ul>\n<li>user login and info: <a class=\"reference external\" href=\"https:\/\/youtu.be\/uftgXppUvXo\">https:\/\/youtu.be\/uftgXppUvXo<\/a><\/li>\n<li>change dequeue: <a class=\"reference external\" href=\"https:\/\/youtu.be\/aELxXcafXQ4\">https:\/\/youtu.be\/aELxXcafXQ4<\/a><\/li>\n<li>reenqueue buildset: <a class=\"reference external\" href=\"https:\/\/youtu.be\/IheWxITSNYQ\">https:\/\/youtu.be\/IheWxITSNYQ<\/a><\/li>\n<li>create autohold request from failed build: <a class=\"reference external\" href=\"https:\/\/youtu.be\/MdZZOxjAQ5M\">https:\/\/youtu.be\/MdZZOxjAQ5M<\/a><\/li>\n<li>autoholds page: <a class=\"reference external\" href=\"https:\/\/youtu.be\/QdaQ2vGLBag\">https:\/\/youtu.be\/QdaQ2vGLBag<\/a><\/li>\n<li>promote a change: <a class=\"reference external\" href=\"https:\/\/youtu.be\/-KPeGEkH9Ak\">https:\/\/youtu.be\/-KPeGEkH9Ak<\/a><\/li>\n<li>free-form autohold creation: <a class=\"reference external\" href=\"https:\/\/youtu.be\/v3AlgyDWEBs\">https:\/\/youtu.be\/v3AlgyDWEBs<\/a><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We added autologin as kibana user to the Kibana service<\/li>\n<li>We ported autologin feature to ansible role elasticsearch recheck<\/li>\n<li>We created new admin role that will be configured for each tenant and also added roles for curator and logstash (also for each tenants)<\/li>\n<li>We integrate logstash service with ansible role elasticsearch recheck<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2021 Jul 01 to Jul 21 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2021-jul-01-to-jul-21-summary.html","rel":"alternate"}},"published":"2021-07-21T10:00:00+00:00","updated":"2021-07-21T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2021-07-21:\/sprint-2021-jul-01-to-jul-21-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We proposed a deployment of gerritbot-matrix: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/opendev\/system-config\/+\/800506\/\">https:\/\/review.opendev.org\/c\/opendev\/system-config\/+\/800506\/<\/a><\/li>\n<li>We reviewed the new zuul-operator implementation: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/q\/topic:%22kopf%22\">https:\/\/review.opendev.org\/q\/topic:%22kopf%22<\/a><\/li>\n<li>We're helping with implementing proper CORS support in zuul-web <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/767691\">https \u2026<\/a><\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We proposed a deployment of gerritbot-matrix: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/opendev\/system-config\/+\/800506\/\">https:\/\/review.opendev.org\/c\/opendev\/system-config\/+\/800506\/<\/a><\/li>\n<li>We reviewed the new zuul-operator implementation: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/q\/topic:%22kopf%22\">https:\/\/review.opendev.org\/q\/topic:%22kopf%22<\/a><\/li>\n<li>We're helping with implementing proper CORS support in zuul-web <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/767691\">https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/767691<\/a><\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We finalized nodepool containers review <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-config\/+\/22110\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-config\/+\/22110<\/a><\/li>\n<li>We fixed issues for telegraf 1.19 <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-config\/+\/22315\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-config\/+\/22315<\/a><\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2021 Jun 10 to Jun 30 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2021-jun-10-to-jun-30-summary.html","rel":"alternate"}},"published":"2021-06-30T10:00:00+00:00","updated":"2021-06-30T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2021-06-30:\/sprint-2021-jun-10-to-jun-30-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We worked on fixing job issues caused by the Zuul 4.6 release: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/opendev\/base-jobs\/+\/797960\">https:\/\/review.opendev.org\/c\/opendev\/base-jobs\/+\/797960<\/a> and <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul-jobs\/+\/798087\">https:\/\/review.opendev.org\/c\/zuul\/zuul-jobs\/+\/798087<\/a><\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We packaged and deployed the latest \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We worked on fixing job issues caused by the Zuul 4.6 release: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/opendev\/base-jobs\/+\/797960\">https:\/\/review.opendev.org\/c\/opendev\/base-jobs\/+\/797960<\/a> and <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul-jobs\/+\/798087\">https:\/\/review.opendev.org\/c\/zuul\/zuul-jobs\/+\/798087<\/a><\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We packaged and deployed the latest Zuul 4.6 release<\/li>\n<li>We worked on a guide to use software-factory as thrid-party-ci for gerrit, gitlab and pagure<\/li>\n<li>We worked on containerized nodepool role and updated zuul role with zuul 4.6.0<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"SF 3.6 minor release to prepare the upcoming Zuul Security Fix","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sf-36-minor-release-to-prepare-the-upcoming-zuul-security-fix.html","rel":"alternate"}},"published":"2021-06-21T00:00:00+00:00","updated":"2021-06-21T00:00:00+00:00","author":{"name":"sf"},"id":"tag:www.softwarefactory-project.io,2021-06-21:\/sf-36-minor-release-to-prepare-the-upcoming-zuul-security-fix.html","summary":"<p>An important Zuul security update is going to be available next Thursday.\nWe've released a minor update to sf 3.6 today with zuul 4.5.0 packages.\nWe recommend to upgrade your system to this sub release before Thursday 24 June 14:00 UTC.\nWe will release a minor \u2026<\/p>","content":"<p>An important Zuul security update is going to be available next Thursday.\nWe've released a minor update to sf 3.6 today with zuul 4.5.0 packages.\nWe recommend to upgrade your system to this sub release before Thursday 24 June 14:00 UTC.\nWe will release a minor release with zuul 4.6 next Thursday.<\/p>\n<p>Make sure your deployment is up to date by running <strong>sfconfig --update<\/strong>.<\/p>\n<p>If you experience any difficulties, please don't hesitate to raise an issue.<\/p>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Upcoming Zuul Security Fix","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/upcoming-zuul-security-fix.html","rel":"alternate"}},"published":"2021-06-10T00:00:00+00:00","updated":"2021-06-10T00:00:00+00:00","author":{"name":"sf"},"id":"tag:www.softwarefactory-project.io,2021-06-10:\/upcoming-zuul-security-fix.html","summary":"<p>An important Zuul security update is going to be available for the SF-3.6 release.\nMake sure your deployment is up to date by running <strong>sfconfig --update<\/strong>.<\/p>\n<p>Please check the upstream announcement: <a class=\"reference external\" href=\"http:\/\/lists.zuul-ci.org\/pipermail\/zuul-announce\/2021-June\/000094.html\">http:\/\/lists.zuul-ci.org\/pipermail\/zuul-announce\/2021-June\/000094.html<\/a><\/p>\n<p>On 2021-06-24 14:00:00 UTC,\nthe new zuul \u2026<\/p>","content":"<p>An important Zuul security update is going to be available for the SF-3.6 release.\nMake sure your deployment is up to date by running <strong>sfconfig --update<\/strong>.<\/p>\n<p>Please check the upstream announcement: <a class=\"reference external\" href=\"http:\/\/lists.zuul-ci.org\/pipermail\/zuul-announce\/2021-June\/000094.html\">http:\/\/lists.zuul-ci.org\/pipermail\/zuul-announce\/2021-June\/000094.html<\/a><\/p>\n<p>On 2021-06-24 14:00:00 UTC,\nthe new zuul package will be published, make sure you turn off the service and perform another <strong>sfconfig --update<\/strong>.\nTo make the update faster, you can run:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"c1\"># From the install-server, update zuul:<\/span>\nansible<span class=\"w\"> <\/span>-m<span class=\"w\"> <\/span><span class=\"nb\">command<\/span><span class=\"w\"> <\/span>-a<span class=\"w\"> <\/span><span class=\"s2\">&quot;yum update -y *zuul*&quot;<\/span><span class=\"w\"> <\/span>install-server:zuul-scheduler:zuul-merger:zuul-executor:zuul-web\nansible-playbook<span class=\"w\"> <\/span>\/var\/lib\/software-factory\/ansible\/zuul_restart.yml\n<\/pre><\/div>\n<p>Note that we are not able to fix the Zuul version 3.19 of the SF-3.5 release, thus\nthe 3.5 version is now End of life. If you are using SF-3.5, update now to SF-3.6 using:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"c1\"># From the install-server, update from sf-3.5 to sf-3.6<\/span>\nyum<span class=\"w\"> <\/span>install<span class=\"w\"> <\/span>-y<span class=\"w\"> <\/span>https:\/\/softwarefactory-project.io\/repos\/sf-release-3.6.rpm\nyum<span class=\"w\"> <\/span>update<span class=\"w\"> <\/span>-y<span class=\"w\"> <\/span>sf-config\nsfconfig<span class=\"w\"> <\/span>--update\n<\/pre><\/div>\n<p>If you experience any difficulties, please don't hesistate to raise an issue.<\/p>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2021 May 20 to Jun 09 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2021-may-20-to-jun-09-summary.html","rel":"alternate"}},"published":"2021-06-09T10:00:00+00:00","updated":"2021-06-09T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2021-06-09:\/sprint-2021-may-20-to-jun-09-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We investigated using Matrix instead of IRC<\/li>\n<li>We investigated a Zuul scheduler issue where the gerrit connection is not restored after a restart<\/li>\n<li>We've improved the search pages for builds and buildsets in the UI (autocompletion for some \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We investigated using Matrix instead of IRC<\/li>\n<li>We investigated a Zuul scheduler issue where the gerrit connection is not restored after a restart<\/li>\n<li>We've improved the search pages for builds and buildsets in the UI (autocompletion for some fields, checkboxes, pagination)<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We worked on containerized zuul story for SF 4.0 <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-config\/+\/21791\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/sf-config\/+\/21791<\/a><\/li>\n<li>We integrated the latest version of Zuul and Nodepool<\/li>\n<li>We investigated using Matrix instead of IRC<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Moving from IRC to Matrix","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/moving-from-irc-to-matrix.html","rel":"alternate"}},"published":"2021-06-09T10:00:00+00:00","updated":"2021-06-09T10:00:00+00:00","author":{"name":"sf"},"id":"tag:www.softwarefactory-project.io,2021-06-09:\/moving-from-irc-to-matrix.html","content":"<p>We are moving from IRC to Matrix, you can now find us at <em>#softwarefactory-project:matrix.org<\/em>.<\/p>\n<p>Setup a matrix client by visiting: <a class=\"reference external\" href=\"https:\/\/matrix.to\">https:\/\/matrix.to<\/a>\/#\/#softwarefactory-project:matrix.org<\/p>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Practical Haskell Use Cases","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/practical-haskell-use-cases.html","rel":"alternate"}},"published":"2021-06-07T00:00:00+00:00","updated":"2021-06-07T00:00:00+00:00","author":{"name":"tristanC"},"id":"tag:www.softwarefactory-project.io,2021-06-07:\/practical-haskell-use-cases.html","summary":"<style type=\"text\/css\">\n\n  .literal {\n    border-radius: 6px;\n    padding: 1px 1px;\n    background-color: rgba(27,31,35,.05);\n  }\n\n<\/style><!-- This work is licensed under the Creative Commons Attribution 4.0 International License.\n     To view a copy of this license, visit http:\/\/creativecommons.org\/licenses\/by\/4.0\/\n     or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.\n--><p>This post presents a few practical projects in which we used Haskell\nsuccesfully.<\/p>\n<p>After using Python type annotations, and then the OCaml type system, a\ncolleague and I started to use Haskell to better define our program. We \u2026<\/p>","content":"<style type=\"text\/css\">\n\n  .literal {\n    border-radius: 6px;\n    padding: 1px 1px;\n    background-color: rgba(27,31,35,.05);\n  }\n\n<\/style><!-- This work is licensed under the Creative Commons Attribution 4.0 International License.\n     To view a copy of this license, visit http:\/\/creativecommons.org\/licenses\/by\/4.0\/\n     or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.\n--><p>This post presents a few practical projects in which we used Haskell\nsuccesfully.<\/p>\n<p>After using Python type annotations, and then the OCaml type system, a\ncolleague and I started to use Haskell to better define our program. We\nare satisfied with the initial results, and it is my pleasure to share\nour use cases.<\/p>\n<div class=\"section\" id=\"lentille-a-bugzilla-task-data-crawler\">\n<h2>Lentille: a bugzilla task data crawler<\/h2>\n<p>Our goal was to perform Bugzilla API data processing. The challenge was\nto query a HTTP API and adapt the responses for our needs.<\/p>\n<p>Fortunately, a client library for <a class=\"reference external\" href=\"https:\/\/hackage.haskell.org\/package\/bugzilla-redhat\">bugzilla-redhat<\/a> already existed. It\nfeatures a convenient <a class=\"reference external\" href=\"https:\/\/hackage.haskell.org\/package\/bugzilla-redhat-0.3.1\/docs\/Web-Bugzilla-RedHat-Search.html\">Search<\/a> module to define search expressions.\nThis allowed us to define our query using type safe operators with this\nexpression:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"nf\">searchExpr<\/span><span class=\"w\"> <\/span><span class=\"ow\">::<\/span><span class=\"w\"> <\/span><span class=\"kt\">UTCTime<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">Text<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">SearchExpression<\/span>\n<span class=\"nf\">searchExpr<\/span><span class=\"w\"> <\/span><span class=\"n\">sinceTS<\/span><span class=\"w\"> <\/span><span class=\"n\">product<\/span><span class=\"w\"> <\/span><span class=\"ow\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">since<\/span><span class=\"w\"> <\/span><span class=\"o\">.&amp;&amp;.<\/span><span class=\"w\"> <\/span><span class=\"n\">linkId<\/span><span class=\"w\"> <\/span><span class=\"o\">.&amp;&amp;.<\/span><span class=\"w\"> <\/span><span class=\"n\">productField<\/span>\n<span class=\"w\">  <\/span><span class=\"kr\">where<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">linkId<\/span><span class=\"w\"> <\/span><span class=\"ow\">=<\/span><span class=\"w\"> <\/span><span class=\"kt\">BZS<\/span><span class=\"o\">.<\/span><span class=\"n\">isNotEmpty<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"kt\">BZS<\/span><span class=\"o\">.<\/span><span class=\"kt\">CustomField<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;ext_bz_bug_map.ext_bz_bug_id&quot;<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">productField<\/span><span class=\"w\"> <\/span><span class=\"ow\">=<\/span><span class=\"w\"> <\/span><span class=\"kt\">BZS<\/span><span class=\"o\">.<\/span><span class=\"kt\">ProductField<\/span><span class=\"w\"> <\/span><span class=\"o\">.==.<\/span><span class=\"w\"> <\/span><span class=\"n\">product<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">since<\/span><span class=\"w\"> <\/span><span class=\"ow\">=<\/span><span class=\"w\"> <\/span><span class=\"kt\">BZS<\/span><span class=\"o\">.<\/span><span class=\"n\">changedSince<\/span><span class=\"w\"> <\/span><span class=\"n\">sinceTS<\/span>\n<\/pre><\/div>\n<p>Then we used the <a class=\"reference external\" href=\"https:\/\/hackage.haskell.org\/package\/streaming\">streaming<\/a> library to isolate the queries from the\nprocessing. This provided an abstraction to handle the results in bulk\n(independently from the pagination logic). Here is the fetching\nfunction, using <a class=\"reference external\" href=\"https:\/\/hackage.haskell.org\/package\/retry\">retry<\/a> to handle network interruptions:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"nf\">getBZData<\/span><span class=\"w\"> <\/span><span class=\"ow\">::<\/span><span class=\"w\"> <\/span><span class=\"kt\">MonadIO<\/span><span class=\"w\"> <\/span><span class=\"n\">m<\/span><span class=\"w\"> <\/span><span class=\"ow\">=&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">BugzillaSession<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">Text<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">UTCTime<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">Stream<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"kt\">Of<\/span><span class=\"w\"> <\/span><span class=\"kt\">TaskData<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"n\">m<\/span><span class=\"w\"> <\/span><span class=\"nb\">()<\/span>\n<span class=\"nf\">getBZData<\/span><span class=\"w\"> <\/span><span class=\"n\">bzSession<\/span><span class=\"w\"> <\/span><span class=\"n\">product<\/span><span class=\"w\"> <\/span><span class=\"n\">since<\/span><span class=\"w\"> <\/span><span class=\"ow\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">go<\/span><span class=\"w\"> <\/span><span class=\"mi\">0<\/span>\n<span class=\"w\">  <\/span><span class=\"kr\">where<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">limit<\/span><span class=\"w\"> <\/span><span class=\"ow\">=<\/span><span class=\"w\"> <\/span><span class=\"mi\">100<\/span>\n\n<span class=\"w\">    <\/span><span class=\"n\">doGet<\/span><span class=\"w\"> <\/span><span class=\"ow\">::<\/span><span class=\"w\"> <\/span><span class=\"kt\">MonadIO<\/span><span class=\"w\"> <\/span><span class=\"n\">m<\/span><span class=\"w\"> <\/span><span class=\"ow\">=&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">Int<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"n\">m<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"kt\">Bug<\/span><span class=\"p\">]<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">doGet<\/span><span class=\"w\"> <\/span><span class=\"n\">offset<\/span><span class=\"w\"> <\/span><span class=\"ow\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">liftIO<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">getBugs<\/span><span class=\"w\"> <\/span><span class=\"n\">bzSession<\/span><span class=\"w\"> <\/span><span class=\"n\">sinceTS<\/span><span class=\"w\"> <\/span><span class=\"n\">product<\/span><span class=\"w\"> <\/span><span class=\"n\">limit<\/span><span class=\"w\"> <\/span><span class=\"n\">offset<\/span><span class=\"p\">)<\/span>\n\n<span class=\"w\">    <\/span><span class=\"n\">go<\/span><span class=\"w\"> <\/span><span class=\"n\">offset<\/span><span class=\"w\"> <\/span><span class=\"ow\">=<\/span><span class=\"w\"> <\/span><span class=\"kr\">do<\/span>\n<span class=\"w\">      <\/span><span class=\"c1\">-- Retrieve rhbz<\/span>\n<span class=\"w\">      <\/span><span class=\"n\">bugs<\/span><span class=\"w\"> <\/span><span class=\"ow\">&lt;-<\/span><span class=\"w\"> <\/span><span class=\"n\">lift<\/span><span class=\"w\"> <\/span><span class=\"o\">$<\/span><span class=\"w\"> <\/span><span class=\"kr\">do<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">log<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"kt\">LogGetBugs<\/span><span class=\"w\"> <\/span><span class=\"n\">sinceTS<\/span><span class=\"w\"> <\/span><span class=\"n\">offset<\/span><span class=\"w\"> <\/span><span class=\"n\">limit<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">retry<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">doGet<\/span><span class=\"w\"> <\/span><span class=\"n\">offset<\/span><span class=\"p\">)<\/span>\n\n<span class=\"w\">      <\/span><span class=\"c1\">-- Create a flat stream of task data<\/span>\n<span class=\"w\">      <\/span><span class=\"kt\">S<\/span><span class=\"o\">.<\/span><span class=\"n\">each<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">concatMap<\/span><span class=\"w\"> <\/span><span class=\"n\">toTaskData<\/span><span class=\"w\"> <\/span><span class=\"n\">bugs<\/span><span class=\"p\">)<\/span>\n\n<span class=\"w\">      <\/span><span class=\"c1\">-- Keep on retrieving the rest<\/span>\n<span class=\"w\">      <\/span><span class=\"n\">unless<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">length<\/span><span class=\"w\"> <\/span><span class=\"n\">bugs<\/span><span class=\"w\"> <\/span><span class=\"o\">&lt;<\/span><span class=\"w\"> <\/span><span class=\"n\">limit<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">go<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">offset<\/span><span class=\"w\"> <\/span><span class=\"o\">+<\/span><span class=\"w\"> <\/span><span class=\"n\">length<\/span><span class=\"w\"> <\/span><span class=\"n\">bugs<\/span><span class=\"p\">))<\/span>\n<\/pre><\/div>\n<p>And here is the stream processing function:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"c1\">-- Group by chunk of 500<\/span>\n<span class=\"nf\">process<\/span><span class=\"w\"> <\/span><span class=\"ow\">::<\/span><span class=\"w\"> <\/span><span class=\"kt\">MonadIO<\/span><span class=\"w\"> <\/span><span class=\"n\">m<\/span><span class=\"w\"> <\/span><span class=\"ow\">=&gt;<\/span><span class=\"w\"> <\/span><span class=\"p\">([<\/span><span class=\"kt\">TaskData<\/span><span class=\"p\">]<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"n\">m<\/span><span class=\"w\"> <\/span><span class=\"kt\">AddResponse<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">Stream<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"kt\">Of<\/span><span class=\"w\"> <\/span><span class=\"kt\">TaskData<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"n\">m<\/span><span class=\"w\"> <\/span><span class=\"nb\">()<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"n\">m<\/span><span class=\"w\"> <\/span><span class=\"nb\">()<\/span>\n<span class=\"nf\">process<\/span><span class=\"w\"> <\/span><span class=\"n\">postFunc<\/span><span class=\"w\"> <\/span><span class=\"ow\">=<\/span>\n<span class=\"w\">  <\/span><span class=\"kt\">S<\/span><span class=\"o\">.<\/span><span class=\"n\">print<\/span>\n<span class=\"w\">    <\/span><span class=\"o\">.<\/span><span class=\"w\"> <\/span><span class=\"kt\">S<\/span><span class=\"o\">.<\/span><span class=\"n\">mapM<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">processBatch<\/span><span class=\"w\"> <\/span><span class=\"n\">postFunc<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">    <\/span><span class=\"o\">.<\/span><span class=\"w\"> <\/span><span class=\"kt\">S<\/span><span class=\"o\">.<\/span><span class=\"n\">mapped<\/span><span class=\"w\"> <\/span><span class=\"kt\">S<\/span><span class=\"o\">.<\/span><span class=\"n\">toList<\/span><span class=\"w\">  <\/span><span class=\"c1\">-- Convert to list (type is Stream (Of [TaskData]) m ())<\/span>\n<span class=\"w\">    <\/span><span class=\"o\">.<\/span><span class=\"w\"> <\/span><span class=\"kt\">S<\/span><span class=\"o\">.<\/span><span class=\"n\">chunksOf<\/span><span class=\"w\"> <\/span><span class=\"mi\">500<\/span><span class=\"w\">     <\/span><span class=\"c1\">-- Chop the stream (type is Stream (Stream (Of TaskData) m) m ())<\/span>\n<\/pre><\/div>\n<p>The client library missed a few features that we were able to implement\nlocally. It was easy to integrate the work in progress changes using a\n<tt class=\"docutils literal\">cabal.project<\/tt> file to override the location of a build dependency.\nFor example, we added <a class=\"reference external\" href=\"https:\/\/github.com\/juhp\/hsbugzilla\/pull\/15\/files\">support for apikey<\/a>.<\/p>\n<\/div>\n<div class=\"section\" id=\"monocle-http-api-based-on-protobuf\">\n<h2>Monocle HTTP API based on Protobuf<\/h2>\n<p>Satisfied with the result of Lentille, we wanted to leverage this\nstrongly typed approach for the API. The goal was to ensure the backend,\nthe workers, and the frontend would use a common and well defined API.\nCheck out this <a class=\"reference external\" href=\"https:\/\/github.com\/change-metrics\/monocle\/blob\/master\/doc\/adr\/0010-choice-of-protobuf.md\">Architecture Decision Record<\/a> for more info.<\/p>\n<p>For consistency with the existing code, we used the Protobuf JSON\nencoding over HTTP. This allowed us to write a simple code generator for\njavascript <tt class=\"docutils literal\">axios<\/tt> client and python <tt class=\"docutils literal\">flask<\/tt> endpoint using the\n<a class=\"reference external\" href=\"https:\/\/hackage.haskell.org\/package\/language-protobuf\">language-protobuf<\/a> library. However we had issues with inconsistent\nJSON encoding. For example, this protobuf message:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"kd\">message<\/span><span class=\"w\"> <\/span><span class=\"nc\">AddResponse<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">  <\/span><span class=\"k\">oneof<\/span><span class=\"w\"> <\/span><span class=\"n\">result<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">TaskDataCommitSuccess<\/span><span class=\"w\"> <\/span><span class=\"na\">success<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"mi\">1<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">TaskDataCommitError<\/span><span class=\"w\"> <\/span><span class=\"na\">error<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"mi\">2<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">  <\/span><span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<p>... has two encodings: the python implementation produces\n<tt class=\"docutils literal\">{&quot;result&quot;: {&quot;success&quot;: <span class=\"pre\">&quot;ok&quot;}}<\/span><\/tt> while the ocaml implementation expects\n<tt class=\"docutils literal\">{&quot;success&quot;: &quot;ok&quot;}<\/tt>. Fortunately, the Haskell implementation\n<a class=\"reference external\" href=\"https:\/\/hackage.haskell.org\/package\/proto3-suite\">proto3-suite<\/a> correctly handles both formats.<\/p>\n<p>Another issue that came up was about the Timestamp message from the\nGoogle protobuf well known type library. The official\n<tt class=\"docutils literal\"><span class=\"pre\">protoc-compiler<\/span><\/tt> transparently encodes this message as a rfc3339\nstring. We had to create a <a class=\"reference external\" href=\"https:\/\/github.com\/awakesecurity\/proto3-suite\/pull\/150\">custom timestamp decoder<\/a>.<\/p>\n<\/div>\n<div class=\"section\" id=\"monocle-search-query\">\n<h2>Monocle Search Query<\/h2>\n<p>Our goal was to improve the query interface by replacing a filters form\nwith a query language. The challenge was to support text based query\nsuch as\n<tt class=\"docutils literal\">(repo:openstack\/nova or repo:openstack\/ironic) and score&gt;200<\/tt>. Check\nout the <a class=\"reference external\" href=\"https:\/\/github.com\/change-metrics\/monocle\/blob\/master\/doc\/adr\/0011-search-query-language.md\">language architecture decision record<\/a> for more info.<\/p>\n<p>Inspired by the work of Gabriel Gonzalez on interpreters, we used\n<a class=\"reference external\" href=\"https:\/\/hackage.haskell.org\/package\/megaparsec\">megaparsec<\/a> to implement the language:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"nf\">lex<\/span><span class=\"w\"> <\/span><span class=\"ow\">::<\/span><span class=\"w\"> <\/span><span class=\"kt\">Text<\/span><span class=\"w\">             <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">Either<\/span><span class=\"w\"> <\/span><span class=\"kt\">ParseError<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"kt\">LocatedToken<\/span><span class=\"p\">]<\/span>\n<span class=\"nf\">parse<\/span><span class=\"w\"> <\/span><span class=\"ow\">::<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"kt\">LocatedToken<\/span><span class=\"p\">]<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">Either<\/span><span class=\"w\"> <\/span><span class=\"kt\">ParseError<\/span><span class=\"w\"> <\/span><span class=\"kt\">Expr<\/span>\n<span class=\"nf\">compile<\/span><span class=\"w\"> <\/span><span class=\"ow\">::<\/span><span class=\"w\"> <\/span><span class=\"kt\">Expr<\/span><span class=\"w\">         <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">Either<\/span><span class=\"w\"> <\/span><span class=\"kt\">ParseError<\/span><span class=\"w\"> <\/span><span class=\"kt\">Query<\/span>\n<\/pre><\/div>\n<p>The query text was compiled to an Elastic search query with the\n<a class=\"reference external\" href=\"https:\/\/hackage.haskell.org\/package\/bloodhound\">bloodhound<\/a> library and they are served through a <a class=\"reference external\" href=\"https:\/\/hackage.haskell.org\/package\/servant\">servant<\/a> API.\nUsing Servant required enabling complex extensions. Fortunately, the\n<a class=\"reference external\" href=\"https:\/\/docs.servant.dev\/en\/stable\/tutorial\/ApiType.html\">tutorial<\/a> explained everything we needed to know. Here is the new\nsearch API defined as a Haskell type:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"kr\">type<\/span><span class=\"w\"> <\/span><span class=\"kt\">MonocleAPI<\/span><span class=\"w\"> <\/span><span class=\"ow\">=<\/span>\n<span class=\"w\">       <\/span><span class=\"s\">&quot;search_fields&quot;<\/span><span class=\"w\"> <\/span><span class=\"kt\">:&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">ReqBody<\/span><span class=\"w\"> <\/span><span class=\"kt\">&#39;[PBJSON]<\/span><span class=\"w\"> <\/span><span class=\"kt\">FieldsRequest<\/span><span class=\"w\"> <\/span><span class=\"kt\">:&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">Post<\/span><span class=\"w\"> <\/span><span class=\"kt\">&#39;[PBJSON]<\/span><span class=\"w\"> <\/span><span class=\"kt\">FieldsResponse<\/span>\n<span class=\"w\">  <\/span><span class=\"kt\">:&lt;|&gt;<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;changes&quot;<\/span><span class=\"w\"> <\/span><span class=\"kt\">:&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">ReqBody<\/span><span class=\"w\"> <\/span><span class=\"kt\">&#39;[PBJSON]<\/span><span class=\"w\"> <\/span><span class=\"kt\">ChangesQueryRequest<\/span><span class=\"w\"> <\/span><span class=\"kt\">:&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">Post<\/span><span class=\"w\"> <\/span><span class=\"kt\">&#39;[PBJSON]<\/span><span class=\"w\"> <\/span><span class=\"kt\">ChangesQueryResponse<\/span>\n<\/pre><\/div>\n<\/div>\n<div class=\"section\" id=\"lentille-graphql-client-for-github-and-gitlab\">\n<h2>Lentille GraphQL client for GitHub and GitLab<\/h2>\n<p>Our goal was to perform data processing of GraphQL APIs. The challenge\nwas to integrate complex queries defined using an extra language.<\/p>\n<p>We used the <a class=\"reference external\" href=\"https:\/\/hackage.haskell.org\/package\/morpheus-graphql\">morpheus-graphql<\/a> library to compile our GraphQL requests\ninto Haskell functions.<\/p>\n<p>We were able to re-use the streaming api we previously wrote. Here is\nthe fetching function that handles pagination cursor:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"nf\">streamFetch<\/span><span class=\"w\"> <\/span><span class=\"ow\">::<\/span>\n<span class=\"w\">  <\/span><span class=\"p\">(<\/span><span class=\"kt\">MonadIO<\/span><span class=\"w\"> <\/span><span class=\"n\">m<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"kt\">Fetch<\/span><span class=\"w\"> <\/span><span class=\"n\">a<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"kt\">FromJSON<\/span><span class=\"w\"> <\/span><span class=\"n\">a<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"ow\">=&gt;<\/span>\n<span class=\"w\">  <\/span><span class=\"kt\">GitHubGraphClient<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span>\n<span class=\"w\">  <\/span><span class=\"c1\">-- | query Args constructor, the function takes a cursor<\/span>\n<span class=\"w\">  <\/span><span class=\"p\">(<\/span><span class=\"kt\">Text<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">Args<\/span><span class=\"w\"> <\/span><span class=\"n\">a<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span>\n<span class=\"w\">  <\/span><span class=\"c1\">-- | query result adapter<\/span>\n<span class=\"w\">  <\/span><span class=\"p\">(<\/span><span class=\"n\">a<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"kt\">PageInfo<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"kt\">RateLimit<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"kt\">Text<\/span><span class=\"p\">],<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"n\">b<\/span><span class=\"p\">]))<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span>\n<span class=\"w\">  <\/span><span class=\"kt\">Stream<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"kt\">Of<\/span><span class=\"w\"> <\/span><span class=\"n\">b<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"n\">m<\/span><span class=\"w\"> <\/span><span class=\"nb\">()<\/span>\n<span class=\"nf\">streamFetch<\/span><span class=\"w\"> <\/span><span class=\"n\">client<\/span><span class=\"w\"> <\/span><span class=\"n\">mkArgs<\/span><span class=\"w\"> <\/span><span class=\"n\">transformResponse<\/span><span class=\"w\"> <\/span><span class=\"ow\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">go<\/span><span class=\"w\"> <\/span><span class=\"kt\">Nothing<\/span>\n<span class=\"w\">  <\/span><span class=\"kr\">where<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">go<\/span><span class=\"w\"> <\/span><span class=\"n\">pageInfoM<\/span><span class=\"w\"> <\/span><span class=\"ow\">=<\/span><span class=\"w\"> <\/span><span class=\"kr\">do<\/span>\n<span class=\"w\">      <\/span><span class=\"n\">respE<\/span><span class=\"w\"> <\/span><span class=\"ow\">&lt;-<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">fetch<\/span>\n<span class=\"w\">          <\/span><span class=\"p\">(<\/span><span class=\"n\">runGithubGraphRequest<\/span><span class=\"w\"> <\/span><span class=\"n\">client<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">          <\/span><span class=\"p\">(<\/span><span class=\"n\">mkArgs<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">fromMaybe<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"ne\">error<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;Missing endCursor&quot;<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">maybe<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"kt\">Just<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;&quot;<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"n\">endCursor<\/span><span class=\"w\"> <\/span><span class=\"n\">pageInfoM<\/span><span class=\"p\">)))<\/span>\n<span class=\"w\">      <\/span><span class=\"kr\">let<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">pageInfo<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">rateLimit<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">decodingErrors<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">xs<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"ow\">=<\/span><span class=\"w\"> <\/span><span class=\"kr\">case<\/span><span class=\"w\"> <\/span><span class=\"n\">respE<\/span><span class=\"w\"> <\/span><span class=\"kr\">of<\/span>\n<span class=\"w\">            <\/span><span class=\"kt\">Left<\/span><span class=\"w\"> <\/span><span class=\"n\">err<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"ne\">error<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">toText<\/span><span class=\"w\"> <\/span><span class=\"n\">err<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">            <\/span><span class=\"kt\">Right<\/span><span class=\"w\"> <\/span><span class=\"n\">resp<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"n\">transformResponse<\/span><span class=\"w\"> <\/span><span class=\"n\">resp<\/span>\n\n<span class=\"w\">      <\/span><span class=\"c1\">-- TODO: report decoding error<\/span>\n<span class=\"w\">      <\/span><span class=\"n\">unless<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">null<\/span><span class=\"w\"> <\/span><span class=\"n\">decodingErrors<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"ne\">error<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Decoding failed: &quot;<\/span><span class=\"w\"> <\/span><span class=\"o\">&lt;&gt;<\/span><span class=\"w\"> <\/span><span class=\"n\">show<\/span><span class=\"w\"> <\/span><span class=\"n\">decodingErrors<\/span><span class=\"p\">))<\/span>\n<span class=\"w\">      <\/span><span class=\"n\">logStatus<\/span><span class=\"w\"> <\/span><span class=\"n\">pageInfo<\/span><span class=\"w\"> <\/span><span class=\"n\">rateLimit<\/span>\n\n<span class=\"w\">      <\/span><span class=\"c1\">-- Create a stream of &#39;b&#39;<\/span>\n<span class=\"w\">      <\/span><span class=\"kt\">S<\/span><span class=\"o\">.<\/span><span class=\"n\">each<\/span><span class=\"w\"> <\/span><span class=\"n\">xs<\/span>\n\n<span class=\"w\">      <\/span><span class=\"c1\">-- Keep on retrieving the rest, TODO: implement throttle<\/span>\n<span class=\"w\">      <\/span><span class=\"n\">when<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">hasNextPage<\/span><span class=\"w\"> <\/span><span class=\"n\">pageInfo<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">go<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"kt\">Just<\/span><span class=\"w\"> <\/span><span class=\"n\">pageInfo<\/span><span class=\"p\">))<\/span>\n<\/pre><\/div>\n<p>Similar to Servant, using Morpheus GraphQL adds strong guarantees to our\ncode. This comes at the cost of tediously handling complex data types.\nFortunately, Haskell features pattern synonyms, which make the pattern\nmatching on deeply nested structure a bit more manageable. Here is an\nexample pattern to match the labels of a GitHub issue:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"nf\">pattern<\/span><span class=\"w\"> <\/span><span class=\"kt\">IssueLabels<\/span><span class=\"w\"> <\/span><span class=\"n\">nodesLabel<\/span>\n<span class=\"w\">  <\/span><span class=\"ow\">&lt;-<\/span><span class=\"w\"> <\/span><span class=\"kt\">SearchNodesIssue<\/span><span class=\"w\"> <\/span><span class=\"kr\">_<\/span><span class=\"w\"> <\/span><span class=\"kr\">_<\/span><span class=\"w\"> <\/span><span class=\"kr\">_<\/span><span class=\"w\"> <\/span><span class=\"kr\">_<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"kt\">Just<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"kt\">SearchNodesLabelsLabelConnection<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"kt\">Just<\/span><span class=\"w\"> <\/span><span class=\"n\">nodesLabel<\/span><span class=\"p\">)))<\/span><span class=\"w\"> <\/span><span class=\"kr\">_<\/span>\n<\/pre><\/div>\n<\/div>\n<div class=\"section\" id=\"gerritbot-for-matrix\">\n<h2>Gerritbot for Matrix<\/h2>\n<p>The goal was to implement a service to forward Gerrit events to Matrix\nrooms. The challenge was to adapt a stream of events into HTTP queries.<\/p>\n<p>I created interfaces for both processes:<\/p>\n<ul class=\"simple\">\n<li>a matrix client using <a class=\"reference external\" href=\"https:\/\/hackage.haskell.org\/package\/aeson\">aeson<\/a> and <a class=\"reference external\" href=\"https:\/\/hackage.haskell.org\/package\/http-client\">http-client<\/a>, and<\/li>\n<li>a ssh command wrapper using the <a class=\"reference external\" href=\"https:\/\/hackage.haskell.org\/package\/turtle\">turtle<\/a> library.<\/li>\n<\/ul>\n<p>Then I used the <a class=\"reference external\" href=\"https:\/\/hackage.haskell.org\/package\/stm\">stm<\/a> library to implement safe concurrent process.\nHere is the helper function to implement a buffered queue:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"nf\">bufferQueueRead<\/span><span class=\"w\"> <\/span><span class=\"ow\">::<\/span><span class=\"w\"> <\/span><span class=\"kt\">Int<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">TBMQueue<\/span><span class=\"w\"> <\/span><span class=\"n\">a<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">IO<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"n\">a<\/span><span class=\"p\">]<\/span>\n<span class=\"nf\">bufferQueueRead<\/span><span class=\"w\"> <\/span><span class=\"n\">maxTime<\/span><span class=\"w\"> <\/span><span class=\"n\">tqueue<\/span><span class=\"w\"> <\/span><span class=\"ow\">=<\/span><span class=\"w\"> <\/span><span class=\"kr\">do<\/span>\n<span class=\"w\">  <\/span><span class=\"n\">event<\/span><span class=\"w\"> <\/span><span class=\"ow\">&lt;-<\/span><span class=\"w\"> <\/span><span class=\"n\">fromMaybe<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"ne\">error<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;Queue is closed&quot;<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"o\">&lt;$&gt;<\/span><span class=\"w\"> <\/span><span class=\"n\">atomically<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"kt\">TBMQueue<\/span><span class=\"o\">.<\/span><span class=\"n\">readTBMQueue<\/span><span class=\"w\"> <\/span><span class=\"n\">tqueue<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">  <\/span><span class=\"n\">threadDelay<\/span><span class=\"w\"> <\/span><span class=\"n\">maxTime<\/span>\n<span class=\"w\">  <\/span><span class=\"n\">atomically<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">drainQueue<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"n\">event<\/span><span class=\"p\">])<\/span>\n<span class=\"w\">  <\/span><span class=\"kr\">where<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">drainQueue<\/span><span class=\"w\"> <\/span><span class=\"n\">acc<\/span><span class=\"w\"> <\/span><span class=\"ow\">=<\/span><span class=\"w\"> <\/span><span class=\"kr\">do<\/span>\n<span class=\"w\">      <\/span><span class=\"n\">event<\/span><span class=\"w\"> <\/span><span class=\"ow\">&lt;-<\/span><span class=\"w\"> <\/span><span class=\"n\">fromMaybe<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"ne\">error<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;Queue is closed&quot;<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"o\">&lt;$&gt;<\/span><span class=\"w\"> <\/span><span class=\"kt\">TBMQueue<\/span><span class=\"o\">.<\/span><span class=\"n\">tryReadTBMQueue<\/span><span class=\"w\"> <\/span><span class=\"n\">tqueue<\/span>\n<span class=\"w\">      <\/span><span class=\"kr\">case<\/span><span class=\"w\"> <\/span><span class=\"n\">event<\/span><span class=\"w\"> <\/span><span class=\"kr\">of<\/span>\n<span class=\"w\">        <\/span><span class=\"kt\">Nothing<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"n\">pure<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">reverse<\/span><span class=\"w\"> <\/span><span class=\"n\">acc<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">        <\/span><span class=\"kt\">Just<\/span><span class=\"w\"> <\/span><span class=\"n\">ev<\/span><span class=\"w\"> <\/span><span class=\"ow\">-&gt;<\/span><span class=\"w\"> <\/span><span class=\"n\">drainQueue<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">ev<\/span><span class=\"w\"> <\/span><span class=\"kt\">:<\/span><span class=\"w\"> <\/span><span class=\"n\">acc<\/span><span class=\"p\">)<\/span>\n<\/pre><\/div>\n<p>I also used <a class=\"reference external\" href=\"https:\/\/hackage.haskell.org\/package\/optparse-generic-1.4.4\/docs\/Options-Generic.html\">Options.Generic<\/a> to define the CLI API as a Haskell data\ntype:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"kr\">data<\/span><span class=\"w\"> <\/span><span class=\"kt\">CLI<\/span><span class=\"w\"> <\/span><span class=\"n\">w<\/span><span class=\"w\"> <\/span><span class=\"ow\">=<\/span><span class=\"w\"> <\/span><span class=\"kt\">CLI<\/span>\n<span class=\"w\">  <\/span><span class=\"p\">{<\/span><span class=\"w\"> <\/span><span class=\"n\">gerritHost<\/span><span class=\"w\"> <\/span><span class=\"ow\">::<\/span><span class=\"w\"> <\/span><span class=\"n\">w<\/span><span class=\"w\"> <\/span><span class=\"kt\">:::<\/span><span class=\"w\"> <\/span><span class=\"kt\">Text<\/span><span class=\"w\"> <\/span><span class=\"o\">&lt;?&gt;<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;The gerrit host&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">gerritUser<\/span><span class=\"w\"> <\/span><span class=\"ow\">::<\/span><span class=\"w\"> <\/span><span class=\"n\">w<\/span><span class=\"w\"> <\/span><span class=\"kt\">:::<\/span><span class=\"w\"> <\/span><span class=\"kt\">Text<\/span><span class=\"w\"> <\/span><span class=\"o\">&lt;?&gt;<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;The gerrit username&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">matrixUrl<\/span><span class=\"w\"> <\/span><span class=\"ow\">::<\/span><span class=\"w\"> <\/span><span class=\"n\">w<\/span><span class=\"w\"> <\/span><span class=\"kt\">:::<\/span><span class=\"w\"> <\/span><span class=\"kt\">Text<\/span><span class=\"w\"> <\/span><span class=\"o\">&lt;?&gt;<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;The matrix url&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">configFile<\/span><span class=\"w\"> <\/span><span class=\"ow\">::<\/span><span class=\"w\"> <\/span><span class=\"n\">w<\/span><span class=\"w\"> <\/span><span class=\"kt\">:::<\/span><span class=\"w\"> <\/span><span class=\"kt\">FilePath<\/span><span class=\"w\"> <\/span><span class=\"o\">&lt;?&gt;<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;The gerritbot.dhall path&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">syncClient<\/span><span class=\"w\"> <\/span><span class=\"ow\">::<\/span><span class=\"w\"> <\/span><span class=\"n\">w<\/span><span class=\"w\"> <\/span><span class=\"kt\">:::<\/span><span class=\"w\"> <\/span><span class=\"kt\">Bool<\/span><span class=\"w\"> <\/span><span class=\"o\">&lt;?&gt;<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;Sync matrix status (join rooms)&quot;<\/span>\n<span class=\"w\">  <\/span><span class=\"p\">}<\/span>\n<\/pre><\/div>\n<p>... and <a class=\"reference external\" href=\"https:\/\/hackage.haskell.org\/package\/dhall\">Dhall.TH<\/a> to derive data types from the configuration file\nschema:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"kt\">Dhall<\/span><span class=\"o\">.<\/span><span class=\"kt\">TH<\/span><span class=\"o\">.<\/span><span class=\"n\">makeHaskellTypes<\/span>\n<span class=\"w\">  <\/span><span class=\"p\">[<\/span><span class=\"w\"> <\/span><span class=\"kt\">Dhall<\/span><span class=\"o\">.<\/span><span class=\"kt\">TH<\/span><span class=\"o\">.<\/span><span class=\"kt\">MultipleConstructors<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;EventType&quot;<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;.\/src\/EventType.dhall&quot;<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">    <\/span><span class=\"kt\">Dhall<\/span><span class=\"o\">.<\/span><span class=\"kt\">TH<\/span><span class=\"o\">.<\/span><span class=\"kt\">SingleConstructor<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;Channel&quot;<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;Channel&quot;<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;(.\/src\/Config.dhall).Type&quot;<\/span>\n<span class=\"w\">  <\/span><span class=\"p\">]<\/span>\n<\/pre><\/div>\n<p>These strongly type interfaces allowed me to safely add new features\nwithout breaking the service. I was able to keep the service running\nduring development without any interruptions.<\/p>\n<\/div>\n<div class=\"section\" id=\"conclusion\">\n<h2>Conclusion<\/h2>\n<p>Haskell is designed to enable efficient programing. There is a wealth of\nlibraries with which to compose, and thanks to the Haddock documentation\nsystem, we were able to integrate many of them.<\/p>\n<p>The type system makes code refactoring and code review really easy. It\nlets us focus on the core logic without having to worry about entire\nclasses of bugs. In particular, Haskell helps us break monolith programs\ninto well defined and re-usable functions. Being able to move the code\nfearlessly is incredibly powerfull.<\/p>\n<p>Moreover, the Haskell community is constantly producing interesting\nwork. It is fascinating to see such progress in the development of a\nlanguage.<\/p>\n<p>However, the learning curve is rather steep. We spent a lot of time\nfighting with errors produced by the type checker. While the editor\nsupport really helped, getting the code to compile was a challenge.<\/p>\n<p>Haskell compiler is currently very slow, and we had to do extra work to\nkeep the continuous integration build time reasonable. A clean build of\nall our dependencies took 30 minutes, and we had to create a cumbersome\nlayered container to keep the build time under 5 minutes.<\/p>\n<p>The Haskell syntax creates undesirable frictions for new contributors\nbecause it initially looks strange. After getting over the bump, the\nlanguage makes a lot of sense and it is not difficult to learn.<\/p>\n<p>In the end, we are happy with the results, and the benefits of using\nHaskell quickly outweight the cost.<\/p>\n<p>If you liked this article, you might be interested in my other ones:\n<a class=\"reference external\" href=\"https:\/\/www.softwarefactory-project.io\/author\/tristanc.html\">https:\/\/www.softwarefactory-project.io\/author\/tristanc.html<\/a><\/p>\n<p>Thanks for your time!<\/p>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2021 Apr 29 to May 19 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2021-apr-29-to-may-19-summary.html","rel":"alternate"}},"published":"2021-05-19T10:00:00+00:00","updated":"2021-05-19T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2021-05-19:\/sprint-2021-apr-29-to-may-19-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We started to work on SF 4.0 by validating we can run sf-ci-functional-minimal job using zuul containers from opendev<\/li>\n<li>We have fixed logserver display of compressed log files in html format<\/li>\n<li>We fixed the provision-demo option \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We started to work on SF 4.0 by validating we can run sf-ci-functional-minimal job using zuul containers from opendev<\/li>\n<li>We have fixed logserver display of compressed log files in html format<\/li>\n<li>We fixed the provision-demo option when using keycloak<\/li>\n<li>We integrate Opendistro with Keycloak<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2021 Apr 08 to Apr 28 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2021-apr-08-to-apr-28-summary.html","rel":"alternate"}},"published":"2021-04-28T10:00:00+00:00","updated":"2021-04-28T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2021-04-28:\/sprint-2021-apr-08-to-apr-28-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We updated the prometheus client change in zuul and got it approved: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/599209\/\">https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/599209\/<\/a><\/li>\n<li>We updated some long open patches on zuul (web UI) and zuul-client.<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We're investigating on using \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We updated the prometheus client change in zuul and got it approved: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/599209\/\">https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/599209\/<\/a><\/li>\n<li>We updated some long open patches on zuul (web UI) and zuul-client.<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We're investigating on using upstream containers for zuul services for SF 4.0 (POC)<\/li>\n<li>We fixed an issue for gerrit user deletion that was failing in tests: <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/pynotedb\/+\/21711\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/pynotedb\/+\/21711<\/a><\/li>\n<li>We fixed issue related to the new version of lodgeit and gerritbot<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2021 Mar 18 to Apr 07 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2021-mar-18-to-apr-07-summary.html","rel":"alternate"}},"published":"2021-04-07T10:00:00+00:00","updated":"2021-04-07T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2021-04-07:\/sprint-2021-mar-18-to-apr-07-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We made progress on the zuul-runner implementation, the required server side change is now effective in zuul.opendev.org, the cli and base needs some more work to be fully usable though<\/li>\n<li>We fixed a network issue with \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We made progress on the zuul-runner implementation, the required server side change is now effective in zuul.opendev.org, the cli and base needs some more work to be fully usable though<\/li>\n<li>We fixed a network issue with the zuul-operator integration job<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We fixed an issue with new user creation in gerrit caused by unexpected git object schemas, patch is: <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/pynotedb\/+\/21472\">https:\/\/softwarefactory-project.io\/r\/c\/software-factory\/pynotedb\/+\/21472<\/a><\/li>\n<li>We released SF 3.6 <a class=\"reference external\" href=\"https:\/\/www.softwarefactory-project.io\/releases\/3.6\/\">https:\/\/www.softwarefactory-project.io\/releases\/3.6\/<\/a><\/li>\n<li>We switched to openjdk 11 for few services in sf (we would like to check if the new Java will help with Gerrit memory consumption)<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2021 Feb 25 to Mar 17 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2021-feb-25-to-mar-17-summary.html","rel":"alternate"}},"published":"2021-03-18T10:00:00+00:00","updated":"2021-03-18T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2021-03-18:\/sprint-2021-feb-25-to-mar-17-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We reviewed Zuul and Nodepool changes<\/li>\n<li>We investigated an issue with zuul tenant resources metrics related to held node: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/781092\">https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/781092<\/a><\/li>\n<li>We investigated using source event instead of websocket for zuul console \u2026<\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We reviewed Zuul and Nodepool changes<\/li>\n<li>We investigated an issue with zuul tenant resources metrics related to held node: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/781092\">https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/781092<\/a><\/li>\n<li>We investigated using source event instead of websocket for zuul console stream: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/779581\">https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/779581<\/a><\/li>\n<li>We continued implementing the zuul-runner specification<\/li>\n<li>we've updated the admin web ui changes for zuul<\/li>\n<li>we've added an Autoholds\/autohold page in the web UI.<\/li>\n<li>we helped community to fix get-pip.py script after pypi community change the bootstrap script url<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We fixed issues related to upgrading sf-3.5 to sf-3.6<\/li>\n<li>We improved the welcome page<\/li>\n<li>We created the sf-3.6-candidate repository and prepare the main release repository<\/li>\n<li>We discussed a roadmap for software factory version 4<\/li>\n<li>We improved kibana backup script<\/li>\n<li>We improved Kibana configuration after bumping to new version<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Software Factory 4 Roadmap","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/software-factory-4-roadmap.html","rel":"alternate"}},"published":"2021-03-12T00:00:00+00:00","updated":"2021-03-12T00:00:00+00:00","author":{"name":"nhicher"},"id":"tag:www.softwarefactory-project.io,2021-03-12:\/software-factory-4-roadmap.html","summary":"<p>Hello,<\/p>\n<p>Here is the roadmap for Software Factory's next major release. The 4.x release\naims to decouple the base operating system from SF services, so that Software\nFactory can be deployed on RPM based flavors of Linux and containerized all SF\nservices.<\/p>\n<p>The high level steps are:<\/p>\n<ul class=\"simple\">\n<li>4.0 \u2026<\/li><\/ul>","content":"<p>Hello,<\/p>\n<p>Here is the roadmap for Software Factory's next major release. The 4.x release\naims to decouple the base operating system from SF services, so that Software\nFactory can be deployed on RPM based flavors of Linux and containerized all SF\nservices.<\/p>\n<p>The high level steps are:<\/p>\n<ul class=\"simple\">\n<li>4.0: modify sf-config to deploy services as containers instead of packages<ul>\n<li>build custom container images for services that are not available (e.g managesf) and use upstream containers for Zuul, Elasticsearch, Gerrit.<\/li>\n<li>replace <cite>yum install \/ systemctl start<\/cite> by <cite>podman run service<\/cite> for all services.<\/li>\n<li>use --volume option to transparently run the container with host files (e.g. \/etc\/zuul, \/var\/lib\/zuul, \/var\/log\/zuul).<\/li>\n<li>replace Cauth with Keycloak for SSO and user management.<\/li>\n<li>replace RepoXplorer with Monocle.<\/li>\n<\/ul>\n<\/li>\n<li>4.1: improve sf-config tasks to be closer to kubernetes.<ul>\n<li>change the setup workflow to run secrets creation on the install-server.<\/li>\n<li>remove the need to have sf-config installed on each host.<\/li>\n<li>services deployment should be consistent:<ul>\n<li>copy the configuration and secret.<\/li>\n<li>create the systemd unit.<\/li>\n<li>start the service.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/li>\n<li>4.2: implement sf-config as kubernetes operators.<ul>\n<li>update the extra glue such as config-update and cron task as kubernetes resources.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>We are looking forward to this significant tech change, which should simplify\nmaintaining, developing and deploying Software Factory. Let us know what you\nthink!<\/p>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Sprint 2021 Feb 4 to Feb 24 summary","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/sprint-2021-feb-4-to-feb-24-summary.html","rel":"alternate"}},"published":"2021-02-24T10:00:00+00:00","updated":"2021-02-24T10:00:00+00:00","author":{"name":"The Software Factory Team"},"id":"tag:www.softwarefactory-project.io,2021-02-24:\/sprint-2021-feb-4-to-feb-24-summary.html","summary":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We worked on reviewing the change needed to release the zuul and nodepool version 4.0<\/li>\n<li>We worked on efficient rendering of links and ansi escape sequence in the zuul web interface: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/q\/topic:%22web-links-render%22\">https:\/\/review.opendev.org\/q\/topic \u2026<\/a><\/li><\/ul><\/div>","content":"<p>Below are the tasks we worked on during our last sprint.<\/p>\n<div class=\"section\" id=\"opendev\">\n<h2>Opendev<\/h2>\n<ul class=\"simple\">\n<li>We worked on reviewing the change needed to release the zuul and nodepool version 4.0<\/li>\n<li>We worked on efficient rendering of links and ansi escape sequence in the zuul web interface: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/q\/topic:%22web-links-render%22\">https:\/\/review.opendev.org\/q\/topic:%22web-links-render%22<\/a><\/li>\n<li>We investigate a benchmark scenario to measure the overhead of the upcoming change for zuul version 5.0: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/776287\">https:\/\/review.opendev.org\/c\/zuul\/zuul\/+\/776287<\/a><\/li>\n<li>We improved the kubernetes client creation in nodepool: <a class=\"reference external\" href=\"https:\/\/review.opendev.org\/c\/zuul\/nodepool\/+\/777022\">https:\/\/review.opendev.org\/c\/zuul\/nodepool\/+\/777022<\/a><\/li>\n<li>We've added testing to Fedora's python-zuul-client package, and ensured the packaged version is compatible with Zuul 3.19.<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"software-factory\">\n<h2>Software Factory<\/h2>\n<ul class=\"simple\">\n<li>We updated the python requirements needed by the latest version of zuul and nodepool<\/li>\n<li>We fixed issues on sf-ci on rhel to prepare sf 3.6 release<\/li>\n<li>We added alias management on sf-gateway to add alias for tls challenge on gateway.conf<\/li>\n<\/ul>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}},{"title":"Using Dhall to generate Fedora CI Zuul config","link":{"@attributes":{"href":"https:\/\/www.softwarefactory-project.io\/using-dhall-to-generate-fedora-ci-zuul-config.html","rel":"alternate"}},"published":"2021-02-10T00:00:00+00:00","updated":"2021-02-10T00:00:00+00:00","author":{"name":"Fabien"},"id":"tag:www.softwarefactory-project.io,2021-02-10:\/using-dhall-to-generate-fedora-ci-zuul-config.html","summary":"<p>In this article we will show how we leveraged the Dhall language to build a\nlist of jobs for Fedora Zuul CI based on a matrix of values.<\/p>\n<div class=\"section\" id=\"fedora-zuul-ci\">\n<h2>Fedora Zuul CI<\/h2>\n<p>FZCI is an effort to provide Zuul CI for Fedora. Main goals, as stated in <a class=\"reference external\" href=\"https:\/\/fedoraproject.org\/wiki\/Zuul-based-ci\">the project's\nwiki page \u2026<\/a><\/p><\/div>","content":"<p>In this article we will show how we leveraged the Dhall language to build a\nlist of jobs for Fedora Zuul CI based on a matrix of values.<\/p>\n<div class=\"section\" id=\"fedora-zuul-ci\">\n<h2>Fedora Zuul CI<\/h2>\n<p>FZCI is an effort to provide Zuul CI for Fedora. Main goals, as stated in <a class=\"reference external\" href=\"https:\/\/fedoraproject.org\/wiki\/Zuul-based-ci\">the project's\nwiki page<\/a>, are:<\/p>\n<ul class=\"simple\">\n<li>Bring CI infrastructure based on Zuul for projects hosted on pagure.io\nand src.fedoraproject.org.<\/li>\n<li>Provide jobs and workflow of jobs around Pull Requests for Fedora packages\n(distgits on src.fedoraproject.org).<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"dhall\">\n<h2>Dhall<\/h2>\n<p>According to the <a class=\"reference external\" href=\"https:\/\/github.com\/dhall-lang\/dhall-lang\">Dhall project's page on GitHub<\/a>,\nDhall is a programmable configuration language optimized for maintainability.\nYou can think of Dhall as: JSON + functions + types + imports.<\/p>\n<\/div>\n<div class=\"section\" id=\"problem-statement\">\n<h2>Problem statement<\/h2>\n<p>Until recently the Fedora Zuul CI ran Koji scratch build jobs for the X86_64 architecture\nonly. But it was decided to add build jobs for each supported Fedora architecture.<\/p>\n<p>The scratch build job is composed of four variants, one for each Fedora branch\/version plus\nepel8 (master\/rawhide, f33, f32, epel8). It means we have to describe the rpm-scratch-build\njob, with its variants, as follow:<\/p>\n<pre class=\"code YAML literal-block\">\n<span class=\"p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">job<\/span><span class=\"p\">:<\/span><span class=\"w\">\n    <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l-Scalar-Plain\">rpm-scratch-build<\/span><span class=\"w\">\n    <\/span><span class=\"nt\">parent<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l-Scalar-Plain\">common-koji-rpm-build<\/span><span class=\"w\">\n    <\/span><span class=\"nt\">branches<\/span><span class=\"p\">:<\/span><span class=\"w\">\n      <\/span><span class=\"p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"l-Scalar-Plain\">master<\/span><span class=\"w\">\n    <\/span><span class=\"nt\">final<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l-Scalar-Plain\">true<\/span><span class=\"w\">\n    <\/span><span class=\"nt\">provides<\/span><span class=\"p\">:<\/span><span class=\"w\">\n      <\/span><span class=\"p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"l-Scalar-Plain\">repo<\/span><span class=\"w\">\n    <\/span><span class=\"nt\">vars<\/span><span class=\"p\">:<\/span><span class=\"w\">\n      <\/span><span class=\"nt\">arches<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l-Scalar-Plain\">x86_64<\/span><span class=\"w\">\n      <\/span><span class=\"nt\">fetch_artifacts<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l-Scalar-Plain\">true<\/span><span class=\"w\">\n      <\/span><span class=\"nt\">release<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l-Scalar-Plain\">master<\/span><span class=\"w\">\n      <\/span><span class=\"nt\">scratch_build<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l-Scalar-Plain\">true<\/span><span class=\"w\">\n      <\/span><span class=\"nt\">target<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l-Scalar-Plain\">rawhide<\/span><span class=\"w\">\n\n<\/span><span class=\"p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">job<\/span><span class=\"p\">:<\/span><span class=\"w\">\n    <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l-Scalar-Plain\">rpm-scratch-build<\/span><span class=\"w\">\n    <\/span><span class=\"nt\">parent<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l-Scalar-Plain\">common-koji-rpm-build<\/span><span class=\"w\">\n    <\/span><span class=\"nt\">branches<\/span><span class=\"p\">:<\/span><span class=\"w\">\n      <\/span><span class=\"p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"l-Scalar-Plain\">f33<\/span><span class=\"w\">\n    <\/span><span class=\"nt\">final<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l-Scalar-Plain\">true<\/span><span class=\"w\">\n    <\/span><span class=\"nt\">provides<\/span><span class=\"p\">:<\/span><span class=\"w\">\n      <\/span><span class=\"p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"l-Scalar-Plain\">repo<\/span><span class=\"w\">\n    <\/span><span class=\"nt\">vars<\/span><span class=\"p\">:<\/span><span class=\"w\">\n      <\/span><span class=\"nt\">arches<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l-Scalar-Plain\">x86_64<\/span><span class=\"w\">\n      <\/span><span class=\"nt\">fetch_artifacts<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l-Scalar-Plain\">true<\/span><span class=\"w\">\n      <\/span><span class=\"nt\">release<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l-Scalar-Plain\">f33<\/span><span class=\"w\">\n      <\/span><span class=\"nt\">scratch_build<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l-Scalar-Plain\">true<\/span><span class=\"w\">\n      <\/span><span class=\"nt\">target<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l-Scalar-Plain\">f33<\/span><span class=\"w\">\n\n<\/span><span class=\"c1\"># And so on for the other supported branches<\/span><span class=\"w\">\n<\/span><span class=\"nn\">...<\/span>\n<\/pre>\n<p>For the default architecture job (x86_64), we need four variants. We also need to\nsupport five additional architectures, with an exception for epel8 branch where\nthree architectures are supported. Thus we need to describe a total of 21 jobs\n(3 branches * 6 architectures) + (1 branch * 3 architectures).<\/p>\n<p>Furthermore, we need to adapt the job's variables based on the architecture.\nFor instance, non x86_64 jobs do not provide a repository.<\/p>\n<p>Here is the job definition called <cite>rpm-scratch-build-s390x<\/cite> for the master branch\nand the S390X architecture:<\/p>\n<pre class=\"code YAML literal-block\">\n<span class=\"p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"nt\">job<\/span><span class=\"p\">:<\/span><span class=\"w\">\n    <\/span><span class=\"nt\">name<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l-Scalar-Plain\">rpm-scratch-build-s390x<\/span><span class=\"w\">\n    <\/span><span class=\"nt\">parent<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l-Scalar-Plain\">common-koji-rpm-build<\/span><span class=\"w\">\n    <\/span><span class=\"nt\">branches<\/span><span class=\"p\">:<\/span><span class=\"w\">\n      <\/span><span class=\"p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"l-Scalar-Plain\">master<\/span><span class=\"w\">\n    <\/span><span class=\"nt\">dependencies<\/span><span class=\"p\">:<\/span><span class=\"w\">\n      <\/span><span class=\"p-Indicator\">-<\/span><span class=\"w\"> <\/span><span class=\"l-Scalar-Plain\">check-for-arches<\/span><span class=\"w\">\n    <\/span><span class=\"nt\">final<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l-Scalar-Plain\">true<\/span><span class=\"w\">\n    <\/span><span class=\"nt\">vars<\/span><span class=\"p\">:<\/span><span class=\"w\">\n      <\/span><span class=\"nt\">arches<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l-Scalar-Plain\">s390x<\/span><span class=\"w\">\n      <\/span><span class=\"nt\">fetch_artifacts<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l-Scalar-Plain\">false<\/span><span class=\"w\">\n      <\/span><span class=\"nt\">release<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l-Scalar-Plain\">master<\/span><span class=\"w\">\n      <\/span><span class=\"nt\">scratch_build<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l-Scalar-Plain\">true<\/span><span class=\"w\">\n      <\/span><span class=\"nt\">target<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"l-Scalar-Plain\">rawhide<\/span>\n<\/pre>\n<p>To manage that complexity we decided to use dhall-lang to benefit from nice helper\nfunctions such as <cite>map<\/cite>, <cite>filter<\/cite> and <cite>merge<\/cite> but also from strong typing.<\/p>\n<\/div>\n<div class=\"section\" id=\"implementation-of-the-jobs-dhall\">\n<h2>Implementation of the jobs.dhall<\/h2>\n<p>We started by defining what are the Architectures and the Branches.<\/p>\n<div class=\"section\" id=\"dhall-definition-of-architectures\">\n<h3>dhall definition of Architectures<\/h3>\n<p>We define the architectures in the <a class=\"reference external\" href=\"https:\/\/www.softwarefactory-project.io\/demo-codes\/FZCI.dhall\/Arches.dhall\">Arches.dhall<\/a> file,\nwhose content is copied below.\nWe'll follow with an explanation of the contents of the file.<\/p>\n<pre class=\"code literal-block\">\nlet Union = &lt; X86_64 | S390X | PPC64LE | I686 | ARMV7HL | AARCH64 &gt;\n\nlet eq_def =\n      { X86_64 = False\n      , S390X = False\n      , PPC64LE = False\n      , I686 = False\n      , ARMV7HL = False\n      , AARCH64 = False\n      }\n\nin  { Type = Union\n    , default = Union.X86_64\n    , fedora =\n      [ Union.X86_64\n      , Union.S390X\n      , Union.PPC64LE\n      , Union.I686\n      , Union.ARMV7HL\n      , Union.AARCH64\n      ]\n    , epel8 = [ Union.X86_64, Union.PPC64LE, Union.AARCH64 ]\n    , show =\n        \\(arch : Union) -&gt;\n          merge\n            { X86_64 = &quot;x86_64&quot;\n            , S390X = &quot;s390x&quot;\n            , PPC64LE = &quot;ppc64le&quot;\n            , I686 = &quot;i686&quot;\n            , ARMV7HL = &quot;armv7hl&quot;\n            , AARCH64 = &quot;aarch64&quot;\n            }\n            arch\n    , isX86_64 = \\(arch : Union) -&gt; merge (eq_def \/\/ { X86_64 = True }) arch\n    }\n<\/pre>\n<p><cite>Arches.dhall<\/cite> provides, through the <cite>in<\/cite> statement, a record of data and\nfunctions that can be seen as a module.<\/p>\n<p>The <cite>Union<\/cite> let binding is an <a class=\"reference external\" href=\"https:\/\/docs.dhall-lang.org\/tutorials\/Language-Tour.html?highlight=union#unions\">Union type<\/a> where we defined the possible values\nof an Architecture.<\/p>\n<p>The <cite>eq_def<\/cite> binding is a base record that we will use to do pattern matching\non the <cite>Union<\/cite>. This is used by the <cite>isX86_64<\/cite> function\nthat takes an <cite>arch<\/cite> and returns <cite>True<\/cite> if the arch's union value is <cite>X86_64<\/cite>.\nNote the use of the <a class=\"reference external\" href=\"https:\/\/docs.dhall-lang.org\/references\/Built-in-types.html?highlight=union#keyword-merge\">merge<\/a>\nfunction to do the pattern matching on the union.<\/p>\n<p>The show function takes an <cite>arch<\/cite> and return the corresponding string that\nwe will use to render the final yaml.<\/p>\n<p>Here are some usages of our new module.<\/p>\n<pre class=\"code bash literal-block\">\n$<span class=\"w\"> <\/span>dhall<span class=\"w\"> <\/span><span class=\"o\">&lt;&lt;&lt;<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;(.\/Arches.dhall).show (.\/Arches.dhall).default&quot;<\/span><span class=\"w\">\n<\/span><span class=\"s2\">&quot;x86_64&quot;<\/span><span class=\"w\">\n<\/span>$<span class=\"w\"> <\/span>dhall<span class=\"w\"> <\/span><span class=\"o\">&lt;&lt;&lt;<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;let Arches = .\/Arches.dhall in [{ job = { architecture = Arches.Type.PPC64LE }}]&quot;<\/span><span class=\"w\">\n<\/span><span class=\"o\">[<\/span><span class=\"w\"> <\/span><span class=\"o\">{<\/span><span class=\"w\"> <\/span>job.architecture<span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\">\n      <\/span>&lt;<span class=\"w\"> <\/span>AARCH64<span class=\"w\"> <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span>ARMV7HL<span class=\"w\"> <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span>I686<span class=\"w\"> <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span>PPC64LE<span class=\"w\"> <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span>S390X<span class=\"w\"> <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span>X86_64<span class=\"w\"> <\/span>&gt;.PPC64LE<span class=\"w\">\n  <\/span><span class=\"o\">}<\/span><span class=\"w\">\n<\/span><span class=\"o\">]<\/span><span class=\"w\">\n<\/span>$<span class=\"w\"> <\/span>dhall-to-yaml<span class=\"w\"> <\/span><span class=\"o\">&lt;&lt;&lt;<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;let arch=(.\/Arches.dhall).show (.\/Arches.dhall).default in [{job = { architecture =  arch}}]&quot;<\/span><span class=\"w\">\n<\/span>-<span class=\"w\"> <\/span>job:<span class=\"w\">\n    <\/span>architecture:<span class=\"w\"> <\/span>x86_64\n<\/pre>\n<\/div>\n<div class=\"section\" id=\"dhall-definition-of-branches\">\n<h3>dhall definition of Branches<\/h3>\n<p>The same way we have defined architectures, we define branches\nin the <a class=\"reference external\" href=\"https:\/\/www.softwarefactory-project.io\/demo-codes\/FZCI.dhall\/Branches.dhall\">Branches.dhall<\/a> file,\nwhose content is copied below.<\/p>\n<p>We'll follow with an explanation of the contents of the file.<\/p>\n<pre class=\"code literal-block\">\nlet Prelude =\n      https:\/\/prelude.dhall-lang.org\/v17.0.0\/package.dhall sha256:10db3c919c25e9046833df897a8ffe2701dc390fa0893d958c3430524be5a43e\n\nlet Arches = .\/Arches.dhall\n\nlet Union = &lt; Master | F32 | F33 | Epel8 &gt;\n\nlet eq_def = { Master = False, F32 = False, F33 = False, Epel8 = False }\n\nlet show =\n      \\(branch : Union) -&gt;\n        merge\n          { Master = &quot;master&quot;, F32 = &quot;f32&quot;, F33 = &quot;f33&quot;, Epel8 = &quot;epel8&quot; }\n          branch\n\nlet all = [ Union.Master, Union.F33, Union.F32, Union.Epel8 ]\n\nin  { Type = Union\n    , default = Union.Master\n    , all\n    , allText = Prelude.List.map Union Text show all\n    , show\n    , target =\n        \\(branch : Union) -&gt;\n          merge\n            { Master = &quot;rawhide&quot;, F32 = &quot;f32&quot;, F33 = &quot;f33&quot;, Epel8 = &quot;epel8&quot; }\n            branch\n    , arches =\n        \\(branch : Union) -&gt;\n          merge\n            { Master = Arches.fedora\n            , F32 = Arches.fedora\n            , F33 = Arches.fedora\n            , Epel8 = Arches.epel8\n            }\n            branch\n    , isMaster = \\(branch : Union) -&gt; merge (eq_def \/\/ { Master = True }) branch\n    , isEpel8 = \\(branch : Union) -&gt; merge (eq_def \/\/ { Epel8 = True }) branch\n    }\n<\/pre>\n<p>The <a class=\"reference external\" href=\"https:\/\/github.com\/dhall-lang\/dhall-lang\/tree\/v17.0.0\/Prelude\">Prelude<\/a> let binding is the Dhall core library.<\/p>\n<p>Note that we include the <cite>Arches.dhall<\/cite> via a let binding. This way we can define\nthe <cite>arches<\/cite> function that take a <cite>branch<\/cite> as argument and return the branch's supported\narchitectures.<\/p>\n<pre class=\"code bash literal-block\">\n$<span class=\"w\"> <\/span>dhall-to-yaml<span class=\"w\"> <\/span><span class=\"o\">&lt;&lt;&lt;<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;(.\/Branches.dhall).arches &lt; Epel8 | F32 | F33 | Master &gt;.Epel8&quot;<\/span><span class=\"w\">\n\n<\/span>-<span class=\"w\"> <\/span>X86_64<span class=\"w\">\n<\/span>-<span class=\"w\"> <\/span>PPC64LE<span class=\"w\">\n<\/span>-<span class=\"w\"> <\/span>AARCH64\n<\/pre>\n<\/div>\n<div class=\"section\" id=\"jobs-dhall\">\n<h3>jobs.dhall<\/h3>\n<p>Now let's use this two new modules to write the\n<a class=\"reference external\" href=\"https:\/\/www.softwarefactory-project.io\/demo-codes\/FZCI.dhall\/jobs.dhall\">jobs.dhall<\/a> file whose content is copied below.\nThen using <cite>dhall-to-yaml<\/cite> command we'll be able to create the jobs.yaml.<\/p>\n<pre class=\"code literal-block\">\nlet Zuul =\n        ~\/git\/softwarefactory-project.io\/software-factory\/dhall-zuul\/package.dhall\n      ? https:\/\/softwarefactory-project.io\/cgit\/software-factory\/dhall-zuul\/plain\/package.dhall\n\nlet Prelude =\n      https:\/\/prelude.dhall-lang.org\/v17.0.0\/package.dhall sha256:10db3c919c25e9046833df897a8ffe2701dc390fa0893d958c3430524be5a43e\n\nlet Branches = .\/Branches.dhall\n\nlet Arches = .\/Arches.dhall\n\nlet generateRpmBuildJobName\n    : Arches.Type -&gt; Text\n    = \\(arch : Arches.Type) -&gt;\n        let suffix =\n              if Arches.isX86_64 arch then &quot;&quot; else &quot;-&quot; ++ Arches.show arch\n\n        in  &quot;rpm-scratch-build&quot; ++ suffix\n\nlet Arches =\n          Arches\n      \/\/  { extras =\n              Prelude.List.filter\n                Arches.Type\n                ( \\(arch : Arches.Type) -&gt;\n                    Prelude.Bool.not (Arches.isX86_64 arch)\n                )\n                Arches.fedora\n          , scratch-job-names =\n              Prelude.List.map\n                Arches.Type\n                Text\n                (\\(arch : Arches.Type) -&gt; generateRpmBuildJobName arch)\n          }\n\nlet check_for_arches =\n      Zuul.Job::{\n      , name = &quot;check-for-arches&quot;\n      , description = Some &quot;Check the packages needs arches builds&quot;\n      , branches = Some Branches.allText\n      , run = Some &quot;playbooks\/rpm\/check-for-arches.yaml&quot;\n      , vars = Some\n          ( Zuul.Vars.object\n              ( toMap\n                  { arch_jobs =\n                      Zuul.Vars.array\n                        ( Prelude.List.map\n                            Text\n                            Zuul.Vars.Type\n                            Zuul.Vars.string\n                            (Arches.scratch-job-names Arches.extras)\n                        )\n                  }\n              )\n          )\n      , nodeset = Some (Zuul.Nodeset.Name &quot;fedora-33-container&quot;)\n      }\n\nlet common_koji_rpm_build =\n      Zuul.Job::{\n      , name = &quot;common-koji-rpm-build&quot;\n      , abstract = Some True\n      , protected = Some True\n      , description = Some &quot;Base job for RPM build on Fedora Koji&quot;\n      , timeout = Some 21600\n      , nodeset = Some (Zuul.Nodeset.Name &quot;fedora-33-container&quot;)\n      , roles = Some [ { zuul = &quot;zuul-distro-jobs&quot; } ]\n      , run = Some &quot;playbooks\/koji\/build-ng.yaml&quot;\n      , secrets = Some\n        [ Zuul.Job.Secret::{ name = &quot;krb_keytab&quot;, secret = &quot;krb_keytab&quot; } ]\n      }\n\nlet setVars =\n      \\(target : Text) -&gt;\n      \\(release : Text) -&gt;\n      \\(arch : Text) -&gt;\n      \\(fetch_artifacts : Bool) -&gt;\n        Zuul.Vars.object\n          ( toMap\n              { fetch_artifacts = Zuul.Vars.bool fetch_artifacts\n              , scratch_build = Zuul.Vars.bool True\n              , target = Zuul.Vars.string target\n              , release = Zuul.Vars.string release\n              , arches = Zuul.Vars.string arch\n              }\n          )\n\nlet doFetchArtifact\n    : Arches.Type -&gt; Bool\n    = \\(arch : Arches.Type) -&gt; Arches.isX86_64 arch\n\nlet generateRpmBuildJob =\n      \\(branch : Branches.Type) -&gt;\n      \\(arch : Arches.Type) -&gt;\n        Zuul.Job::{\n        , name = generateRpmBuildJobName arch\n        , parent = Some (Zuul.Job.getName common_koji_rpm_build)\n        , final = Some True\n        , provides =\n            if Arches.isX86_64 arch then Some [ &quot;repo&quot; ] else None (List Text)\n        , dependencies =\n            if    Arches.isX86_64 arch\n            then  None (List Zuul.Job.Dependency.Union)\n            else  Some [ Zuul.Job.Dependency.Name &quot;check-for-arches&quot; ]\n        , branches = Some [ Branches.show branch ]\n        , vars = Some\n            ( setVars\n                (Branches.target branch)\n                (Branches.show branch)\n                (Arches.show arch)\n                (doFetchArtifact arch)\n            )\n        }\n\nlet generateRpmScratchBuildJobs\n    : List Zuul.Job.Type\n    = let forBranch =\n            \\(branch : Branches.Type) -&gt;\n              Prelude.List.map\n                Arches.Type\n                Zuul.Job.Type\n                (generateRpmBuildJob branch)\n                (Branches.arches branch)\n\n      in  Prelude.List.concatMap\n            Branches.Type\n            Zuul.Job.Type\n            forBranch\n            Branches.all\n\nlet Jobs =\n      [ check_for_arches, common_koji_rpm_build ] # generateRpmScratchBuildJobs\n\nin  Zuul.Job.wrap Jobs\n<\/pre>\n<p>To write this file we used the <a class=\"reference external\" href=\"https:\/\/github.com\/softwarefactory-project\/dhall-zuul\">Dhall-Zuul Binding library<\/a>. We import\nthe library using the <cite>Zuul<\/cite> let binding.<\/p>\n<p>The <cite>in<\/cite> statement uses\nthe <cite>wrap<\/cite> function provided <cite>dhall-zuul<\/cite> to wrap the list of <a class=\"reference external\" href=\"https:\/\/github.com\/softwarefactory-project\/dhall-zuul\/blob\/master\/Zuul\/Job\/Type.dhall\">Zuul.Jobs.Type<\/a>\nto make this list consumable by Zuul.<\/p>\n<p>The <cite>check-for-arches<\/cite> is a &quot;conditional job&quot; that control the triggering\nof dependent jobs. It needs to be triggered on branches defined in <cite>Branches.dhall<\/cite>.\nThe job's playbook expects a variable called <cite>arch_jobs<\/cite> that is the list of\narchitecture dependent jobs names. The list is built based on <cite>&quot;Arches.dhall&quot;.fedora<\/cite>.<\/p>\n<p>Note the use of <a class=\"reference external\" href=\"https:\/\/docs.dhall-lang.org\/references\/Built-in-types.html?highlight=tomap#keyword-tomap\">toMap<\/a>,\n<a class=\"reference external\" href=\"https:\/\/prelude.dhall-lang.org\/v17.0.0\/List\/map\">List.map<\/a>, and <a class=\"reference external\" href=\"https:\/\/prelude.dhall-lang.org\/v17.0.0\/List\/filter\">List.filter<\/a>\nfunctions.<\/p>\n<p>The <cite>common_koji_rpm_build<\/cite> is the parent job of all scratch build jobs.\nThe Zuul configuration loader will make all child jobs inherit from its\nattributes.<\/p>\n<p>The <cite>Jobs<\/cite> list is extended (using the <a class=\"reference external\" href=\"https:\/\/docs.dhall-lang.org\/references\/Built-in-types.html#id49\">#<\/a> operator) with <cite>generateRpmScratchBuildJobs<\/cite>.<\/p>\n<p><cite>generateRpmScratchBuildJobs<\/cite> is a list of <cite>Zuul.Job.Type<\/cite> built from two encapsulted\niterations over the <cite>Branches.all<\/cite> and <cite>Branches.arches &lt;branch&gt;<\/cite>. Note the use of\n<a class=\"reference external\" href=\"https:\/\/prelude.dhall-lang.org\/v17.0.0\/List\/concatMap\">concatMap<\/a> to flatten\nthe resulting nested lists.<\/p>\n<p>At each iteration the <cite>generateRpmBuildJob<\/cite> function is called by taking\nthe branch and the architecture as arguments.<\/p>\n<p><cite>generateRpmBuildJob<\/cite> defined a <cite>Zuul.Job.Type<\/cite> by setting the job' parameters\nbased on the <cite>branch<\/cite> and <cite>arch<\/cite> context. The <cite>dependencies<\/cite> attributes is\nbuilt using <cite>if\/then\/else<\/cite> statements. The <cite>name<\/cite> attribute is defined\nby the <cite>generateRpmBuildJobName<\/cite> function call as well as <cite>vars<\/cite> is defined by\na call to <cite>setVars<\/cite>.<\/p>\n<p>Let's run dhall-to-yaml command to get the YAML output.<\/p>\n<pre class=\"code bash literal-block\">\n$<span class=\"w\"> <\/span>dhall-to-yaml<span class=\"w\"> <\/span><span class=\"o\">&lt;&lt;&lt;<\/span><span class=\"w\"> <\/span>.\/jobs.dhall<span class=\"w\"> <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span>zuulfmt\n<\/pre>\n<p>Here is the generated <a class=\"reference external\" href=\"https:\/\/www.softwarefactory-project.io\/demo-codes\/FZCI.dhall\/jobs.yaml\">jobs.yaml<\/a> .<\/p>\n<p>Note the use of <a class=\"reference external\" href=\"https:\/\/softwarefactory-project.io\/r\/gitweb?p=software-factory\/zuulfmt.git\">zuulfmt<\/a>\nthats is a tool to format a Zuul config YAML definition.<\/p>\n<\/div>\n<\/div>\n<div class=\"section\" id=\"fedora-distgits-master-branch-removal\">\n<h2>Fedora distgits master branch removal<\/h2>\n<p>On February 3rd, the Fedora community ran the migration to <a class=\"reference external\" href=\"https:\/\/fedoraproject.org\/wiki\/Changes\/GitRepos-master-to-main\">remove the\nmaster branch from the distgit repositories<\/a>.\nFor Zuul configuration, this required some small changes to ensure PRs on main and rawhide\nbranches are handled by Zuul.<\/p>\n<p>To handle this change, we acted in three steps:<\/p>\n<ul class=\"simple\">\n<li><a class=\"reference external\" href=\"https:\/\/pagure.io\/fedora-project-config\/pull-request\/126#request_diff\">Updated the FZCI.dhall package to include the new branches<\/a>.<\/li>\n<li><a class=\"reference external\" href=\"https:\/\/pagure.io\/fedora-zuul-jobs-config\/pull-request\/105#request_diff\">Updated fedora-zuul-jobs-config\/zuul.d\/jobs.dhall and regenerated the jobs.yaml<\/a>.<\/li>\n<li><a class=\"reference external\" href=\"https:\/\/pagure.io\/fedora-zuul-jobs\/pull-request\/98#request_diff\">Updated fedora-zuul-jobs\/zuul.d\/jobs.dhall and regenerated the jobs.yaml<\/a>.<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"support-of-fedora-f34-branch\">\n<h2>Support of Fedora f34 branch<\/h2>\n<p>On February 9th, the branching of Fedora 34 from rawhide happened. Each distgit\nrepository got a <cite>f34<\/cite> branch. For Zuul configuration, this required new job\nvariants to support this new branch. To do so we only changed some dhall files\nthen regenerated the yaml files.<\/p>\n<p>Bellow are the three changes that was required.<\/p>\n<ul class=\"simple\">\n<li><a class=\"reference external\" href=\"https:\/\/pagure.io\/fedora-project-config\/pull-request\/131\">Updated FZCI.dhall package to include the new branch<\/a>.<\/li>\n<li><a class=\"reference external\" href=\"https:\/\/pagure.io\/fedora-zuul-jobs-config\/pull-request\/110\">Regenerated the fedora-zuul-jobs-config\/zuul.d\/jobs.yaml with dhall-to-yaml<\/a>.<\/li>\n<li><a class=\"reference external\" href=\"https:\/\/pagure.io\/fedora-zuul-jobs\/pull-request\/100\">Updated fedora-zuul-jobs\/zuul.d\/jobs.dhall and regenered the jobs.yaml<\/a>.<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"pros-and-cons\">\n<h2>Pros and cons<\/h2>\n<p>Let's see the pros and cons regarding the dhall-lang usage to manage the FZCI jobs:<\/p>\n<div class=\"section\" id=\"cons\">\n<h3>Cons<\/h3>\n<ul class=\"simple\">\n<li>New language to learn for contributors.<\/li>\n<li>Less welcoming for contributors with no previous Dhall experiences.<\/li>\n<li>Not as simple as editing a YAML file.<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"pros\">\n<h3>Pros<\/h3>\n<ul class=\"simple\">\n<li>Dhall-Zuul prevents invalid Zuul job definition. For instance a\ntypo in a job's attribute or using a string as value attribute where a list of strings\nis expected will be caught by the Dhall interpreter.<\/li>\n<li>Dhall IDE integration provides type checking and completion. For instance my VSCode IDE\nwill list the available Branches (from &quot;Branches.dhall&quot;.Type) and prevents me\nto use one not part of the Union.<\/li>\n<li>No more YAML formating issue.<\/li>\n<li>Adding a branch (ex. f34) is less error prone. For instance it is not possible to\nmiss a job for a given Arch, neither setting the wrong jobs' vars.<\/li>\n<li>No more YAML \/ code duplication as it is easy to write functions.<\/li>\n<li>Allow modularization and code reusability.<\/li>\n<\/ul>\n<\/div>\n<\/div>\n<div class=\"section\" id=\"to-conclude\">\n<h2>To conclude<\/h2>\n<p>Thanks to that effort, adding and removing an architecture or a branch is easier\nbecause it is significantly less error prone. We have also started\nto modularize the base definitions (branches, arches) so it will be easy to\nextend the jobs we provide through FZCI.<\/p>\n<p>Thank you for reading!<\/p>\n<\/div>\n","category":{"@attributes":{"term":"blog"}}}]}