<p>As <a href="https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer" rel="nofollow noreferrer">the kubernetes.io docs state about a <code>Service</code> of type <code>LoadBalancer</code></a>:</p>
<blockquote>
<p>On cloud providers which support external load balancers, setting the
type field to LoadBalancer provisions a load balancer for your
Service. The actual creation of the load balancer happens
asynchronously, and information about the provisioned balancer is
published in the Service's <code>.status.loadBalancer</code> field.</p>
</blockquote>
<p>On AWS Elastic Kubernetes Service (EKS) a an AWS Load Balancer is provisioned that load balances network traffic (<a href="https://docs.aws.amazon.com/eks/latest/userguide/network-load-balancing.html" rel="nofollow noreferrer">see AWS docs</a> &amp; <a href="https://github.com/jonashackt/tekton-argocd-eks" rel="nofollow noreferrer">the example project on GitHub provisioning a EKS cluster with Pulumi</a>). Assuming we have a <code>Deployment</code> ready with the selector <code>app=tekton-dashboard</code> (it's the <a href="https://tekton.dev/docs/dashboard/#installation" rel="nofollow noreferrer">default Tekton dashboard you can deploy as stated in the docs</a>), a <code>Service</code> of type <code>LoadBalancer</code> defined in <code>tekton-dashboard-service.yml</code> could look like this:</p>
<pre><code>apiVersion: v1
kind: Service
metadata:
  name: tekton-dashboard-external-svc-manual
spec:
  selector:
    app: tekton-dashboard
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9097
  type: LoadBalancer
</code></pre>
<p>If we create the Service in our cluster with <code>kubectl apply -f tekton-dashboard-service.yml -n tekton-pipelines</code>, the AWS ELB get's created automatically:</p>
<p><a href="https://i.stack.imgur.com/Iq6Mi.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/Iq6Mi.png" alt="enter image description here" /></a></p>
<p>There's only one problem: The <code>.status.loadBalancer</code> field is populated with the <code>ingress[0].hostname</code> field asynchronously and is therefore not available immediately. We can check this, if we run the following commands together:</p>
<pre><code>kubectl apply -f tekton-dashboard-service.yml -n tekton-pipelines &amp;&amp; \
kubectl get service/tekton-dashboard-external-svc-manual -n tekton-pipelines --output=jsonpath='{.status.loadBalancer}'
</code></pre>
<p>The output will be an empty field:</p>
<pre><code>{}%
</code></pre>
<p>So if we want to run this setup in a CI pipeline for example (e.g. <a href="https://github.com/jonashackt/tekton-argocd-eks/blob/main/.github/workflows/provision.yml" rel="nofollow noreferrer">GitHub Actions, see the example project's workflow <code>provision.yml</code></a>), <strong>we need to somehow wait until the <code>.status.loadBalancer</code> field got populated with the AWS ELB's hostname.</strong> How can we achieve this using <code>kubectl wait</code>?</p>

<p><strong>TLDR;</strong></p>
<p><a href="https://github.com/kubernetes/kubernetes/pull/105776" rel="nofollow noreferrer">Prior to Kubernetes <code>v1.23</code></a> it's not possible using <code>kubectl wait</code>, but using <code>until</code> together with <code>grep</code> like this:</p>
<pre><code>until kubectl get service/tekton-dashboard-external-svc-manual -n tekton-pipelines --output=jsonpath='{.status.loadBalancer}' | grep &quot;ingress&quot;; do : ; done
</code></pre>
<p>or even enhance the command <a href="https://stackoverflow.com/a/58759974/4964553">using timeout</a> (<a href="https://stackoverflow.com/a/70109348/4964553"><code>brew install coreutils</code> on a Mac</a>) to prevent the command from running infinitely:</p>
<pre><code>timeout 10s bash -c 'until kubectl get service/tekton-dashboard-external-svc-manual -n tekton-pipelines --output=jsonpath='{.status.loadBalancer}' | grep &quot;ingress&quot;; do : ; done'
</code></pre>
<p><strong>Problem with kubectl wait &amp; the solution explained in detail</strong></p>
<p>As stated in <a href="https://stackoverflow.com/questions/66114851/kubectl-wait-for-service-to-get-external-ip">this so Q&amp;A</a> and the kubernetes issues <a href="https://github.com/kubernetes/kubernetes/issues/80828#issuecomment-517555879" rel="nofollow noreferrer">kubectl wait unable to not wait for service ready #80828</a> &amp; <a href="https://github.com/kubernetes/kubernetes/issues/80828" rel="nofollow noreferrer">kubectl wait on arbitrary jsonpath #83094</a> using <code>kubectl wait</code> for this isn't possible in current Kubernetes versions right now.</p>
<p>The main reason is, that <code>kubectl wait</code> assumes that the <code>status</code> field of a Kubernetes resource queried with <code>kubectl get service/xyz --output=yaml</code> contains a <code>conditions</code> list. Which a <code>Service</code> doesn't have. Using jsonpath here would be a solution and will be possible from Kubernetes <code>v1.23</code> on (see <a href="https://github.com/kubernetes/kubernetes/pull/105776" rel="nofollow noreferrer">this merged PR</a>). But until this version is broadly available in managed Kubernetes clusters like EKS, we need another solution. And it should also be available as &quot;one-liner&quot; just as a <code>kubectl wait</code> would be.</p>
<p>A good starting point could be this superuser answer about <a href="https://superuser.com/a/375331/497608">&quot;watching&quot; the output of a command until a particular string is observed and then exit</a>:</p>
<pre><code>until my_cmd | grep &quot;String Im Looking For&quot;; do : ; done
</code></pre>
<p>If we use this approach together with a <code>kubectl get</code> we can craft a command which will wait until the field <code>ingress</code> gets populated into the <code>status.loadBalancer</code> field in our <code>Service</code>:</p>
<pre><code>until kubectl get service/tekton-dashboard-external-svc-manual -n tekton-pipelines --output=jsonpath='{.status.loadBalancer}' | grep &quot;ingress&quot;; do : ; done
</code></pre>
<p>This will wait until the <code>ingress</code> field got populated and then print out the AWS ELB address (e.g. via using <code>kubectl get service tekton-dashboard-external-svc-manual -n tekton-pipelines --output=jsonpath='{.status.loadBalancer.ingress[0].hostname}'</code> thereafter):</p>
<pre><code>$ until kubectl get service/tekton-dashboard-external-svc-manual -n tekton-pipelines --output=jsonpath='{.status.loadBalancer}' | grep &quot;ingress&quot;; do : ; done
{&quot;ingress&quot;:[{&quot;hostname&quot;:&quot;a74b078064c7d4ba1b89bf4e92586af0-18561896.eu-central-1.elb.amazonaws.com&quot;}]}
</code></pre>
<p>Now we have a one-liner command that behaves just like a <code>kubectl wait</code> for our <code>Service</code> to become available through the AWS Loadbalancer. We can double check if this is working with the following commands combined (be sure to delete the Service using <code>kubectl delete service/tekton-dashboard-external-svc-manual -n tekton-pipelines</code> before you execute it, because otherwise the Service incl. the AWS LoadBalancer already exists):</p>
<pre><code>kubectl apply -f tekton-dashboard-service.yml -n tekton-pipelines &amp;&amp; \
until kubectl get service/tekton-dashboard-external-svc-manual -n tekton-pipelines --output=jsonpath='{.status.loadBalancer}' | grep &quot;ingress&quot;; do : ; done &amp;&amp; \
kubectl get service tekton-dashboard-external-svc-manual -n tekton-pipelines --output=jsonpath='{.status.loadBalancer.ingress[0].hostname}'
</code></pre>
<p><a href="https://github.com/jonashackt/tekton-argocd-eks/runs/4321961432?check_suite_focus=true" rel="nofollow noreferrer">Here's also a full GitHub Actions pipeline run</a> if you're interested.</p>
