@hed-hog/cli 0.0.109 → 0.0.111

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,6 +4,11 @@ on:
4
4
  branches:
5
5
  - <%= config.deployBranch %>
6
6
  workflow_dispatch:
7
+ inputs:
8
+ apps:
9
+ description: 'Apps para deploy (ex: api, admin, api,admin) ou "all" para todos'
10
+ required: false
11
+ default: 'all'
7
12
  env:
8
13
  REGISTRY: <%= config.containerRegistry %>
9
14
  NAMESPACE: <%= config.namespace %>
@@ -18,6 +23,10 @@ env:
18
23
  NEXT_PUBLIC_API_BASE_URL: <%= config.apiDomain || '' %>
19
24
  INTERNAL_API_URL: <%= config.apiDomain || '' %>
20
25
  <% } %>
26
+ <% (config.apps || []).filter(function(a) { return a !== 'api' && a !== 'admin'; }).forEach(function(app) { %>
27
+ # <%= app %> environment
28
+ <%= app.toUpperCase() %>_API_BASE_URL: <%= config.apiDomain || '' %>
29
+ <% }); %>
21
30
  jobs:
22
31
  apply-cluster-config:
23
32
  name: Apply Kubernetes and Helm configs
@@ -53,6 +62,14 @@ jobs:
53
62
  name: Deploy API
54
63
  runs-on: ubuntu-latest
55
64
  needs: apply-cluster-config
65
+ if: >-
66
+ (github.event_name == 'push' &&
67
+ (!contains(github.event.head_commit.message, '[deploy:') ||
68
+ contains(github.event.head_commit.message, '[deploy:api]'))) ||
69
+ (github.event_name == 'workflow_dispatch' &&
70
+ (github.event.inputs.apps == '' ||
71
+ github.event.inputs.apps == 'all' ||
72
+ contains(github.event.inputs.apps, 'api')))
56
73
  # Required GitHub Secrets for API deployment:
57
74
  # - DATABASE_URL: PostgreSQL connection string (format: postgresql://user:pass@host:port/dbname)
58
75
  # - JWT_SECRET: 64-char hex string or base64 key for JWT signing
@@ -361,6 +378,19 @@ jobs:
361
378
  <% if ((config.apps || []).includes('api')) { %>
362
379
  - deploy-api
363
380
  <% } %>
381
+ if: >-
382
+ always() &&
383
+ needs.apply-cluster-config.result == 'success' &&
384
+ <% if ((config.apps || []).includes('api')) { %>
385
+ (needs.deploy-api.result == 'success' || needs.deploy-api.result == 'skipped') &&
386
+ <% } %>
387
+ ((github.event_name == 'push' &&
388
+ (!contains(github.event.head_commit.message, '[deploy:') ||
389
+ contains(github.event.head_commit.message, '[deploy:admin]'))) ||
390
+ (github.event_name == 'workflow_dispatch' &&
391
+ (github.event.inputs.apps == '' ||
392
+ github.event.inputs.apps == 'all' ||
393
+ contains(github.event.inputs.apps, 'admin'))))
364
394
  steps:
365
395
  - name: Checkout code
366
396
  uses: actions/checkout@v6
@@ -534,3 +564,147 @@ jobs:
534
564
  doctl registry repository delete-manifest "${REGISTRY_NAME}/${REPOSITORY}" "${DIGEST}" --force
535
565
  done
536
566
  <% } %>
567
+ <% (config.apps || []).filter(function(a) { return a !== 'api' && a !== 'admin'; }).forEach(function(app) { var appUpper = app.toUpperCase(); var appCapitalized = app.charAt(0).toUpperCase() + app.slice(1); %>
568
+ deploy-<%= app %>:
569
+ name: Deploy <%= appCapitalized %>
570
+ runs-on: ubuntu-latest
571
+ needs:
572
+ - apply-cluster-config
573
+ <% if ((config.apps || []).includes('api')) { %>
574
+ - deploy-api
575
+ <% } %>
576
+ if: >-
577
+ always() &&
578
+ needs.apply-cluster-config.result == 'success' &&
579
+ <% if ((config.apps || []).includes('api')) { %>
580
+ (needs.deploy-api.result == 'success' || needs.deploy-api.result == 'skipped') &&
581
+ <% } %>
582
+ ((github.event_name == 'push' &&
583
+ (!contains(github.event.head_commit.message, '[deploy:') ||
584
+ contains(github.event.head_commit.message, '[deploy:<%= app %>]'))) ||
585
+ (github.event_name == 'workflow_dispatch' &&
586
+ (github.event.inputs.apps == '' ||
587
+ github.event.inputs.apps == 'all' ||
588
+ contains(github.event.inputs.apps, '<%= app %>'))))
589
+ steps:
590
+ - name: Checkout code
591
+ uses: actions/checkout@v6
592
+ - name: Install doctl
593
+ run: |
594
+ set -eu
595
+ DOCTL_VERSION="1.119.1"
596
+ curl -fsSL "https://github.com/digitalocean/doctl/releases/download/v${DOCTL_VERSION}/doctl-${DOCTL_VERSION}-linux-amd64.tar.gz" -o /tmp/doctl.tar.gz
597
+ tar -xzf /tmp/doctl.tar.gz -C /tmp
598
+ sudo mv /tmp/doctl /usr/local/bin/doctl
599
+ doctl version
600
+ - name: Authenticate doctl
601
+ run: doctl auth init -t '${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}'
602
+ - name: Validate DigitalOcean token
603
+ run: |
604
+ doctl auth validate
605
+ doctl account get
606
+ - name: Log in to Container Registry
607
+ run: doctl registry login --expiry-seconds 1200
608
+ - name: Build Docker image
609
+ run: |
610
+ docker build -t ${{ env.REGISTRY }}/<%= config.appName %>-<%= app %>:${{ github.sha }} \
611
+ --build-arg <%= appUpper %>_API_BASE_URL='${{ env.<%= appUpper %>_API_BASE_URL }}' \
612
+ -f apps/<%= app %>/Dockerfile apps/<%= app %>/
613
+ - name: Push Docker image (sha)
614
+ run: |
615
+ docker push ${{ env.REGISTRY }}/<%= config.appName %>-<%= app %>:${{ github.sha }}
616
+ - name: Push Docker image (latest)
617
+ run: |
618
+ docker tag ${{ env.REGISTRY }}/<%= config.appName %>-<%= app %>:${{ github.sha }} \
619
+ ${{ env.REGISTRY }}/<%= config.appName %>-<%= app %>:latest
620
+ docker push ${{ env.REGISTRY }}/<%= config.appName %>-<%= app %>:latest
621
+ - name: Save DigitalOcean kubeconfig
622
+ run: doctl kubernetes cluster kubeconfig save ${{ env.K8S_CLUSTER_ID }}
623
+ - name: Ensure <%= app %> config exists
624
+ run: |
625
+ kubectl create configmap <%= config.appName %>-<%= app %>-config \
626
+ -n ${{ env.NAMESPACE }} \
627
+ --from-literal=<%= appUpper %>_API_BASE_URL='${{ env.<%= appUpper %>_API_BASE_URL }}' \
628
+ --dry-run=client -o yaml | kubectl apply -f -
629
+ - name: Deploy to Kubernetes
630
+ run: |
631
+ kubectl set image deployment/<%= config.appName %>-<%= app %> \
632
+ <%= config.appName %>-<%= app %>=${{ env.REGISTRY }}/<%= config.appName %>-<%= app %>:${{ github.sha }} \
633
+ -n ${{ env.NAMESPACE }}
634
+ kubectl rollout status deployment/<%= config.appName %>-<%= app %> -n ${{ env.NAMESPACE }} --timeout=5m || (
635
+ echo "--- Rollout timed out. Pod status:"
636
+ kubectl get pods -l app=<%= config.appName %>-<%= app %> -n ${{ env.NAMESPACE }}
637
+ echo "--- Recent events:"
638
+ kubectl describe deployment/<%= config.appName %>-<%= app %> -n ${{ env.NAMESPACE }} | tail -20
639
+ POD_NAME=$(kubectl get pods -l app=<%= config.appName %>-<%= app %> -n ${{ env.NAMESPACE }} --sort-by=.metadata.creationTimestamp -o name | tail -n 1 | cut -d/ -f2)
640
+ if [ -n "$POD_NAME" ]; then
641
+ echo "--- Describe newest pod: $POD_NAME"
642
+ kubectl describe pod "$POD_NAME" -n ${{ env.NAMESPACE }}
643
+ echo "--- Current logs from $POD_NAME:"
644
+ kubectl logs "$POD_NAME" -n ${{ env.NAMESPACE }} --tail=120 || true
645
+ fi
646
+ exit 1
647
+ )
648
+ - name: Cleanup old registry manifests (<%= appCapitalized %>)
649
+ run: |
650
+ set -eu
651
+ REGISTRY_NAME="${REGISTRY##*/}"
652
+ REPOSITORY="<%= config.appName %>-<%= app %>"
653
+ DIGEST_REGEX='^sha256:[a-f0-9]{64}$'
654
+
655
+ echo "Inspecting repository ${REPOSITORY} in registry ${REGISTRY_NAME}"
656
+
657
+ TAG_ROWS="$(doctl registry repository list-tags "${REGISTRY_NAME}/${REPOSITORY}" --format Tag,ManifestDigest --no-header || true)"
658
+
659
+ if [ -z "${TAG_ROWS}" ]; then
660
+ echo "No tags found for ${REPOSITORY}; skipping cleanup."
661
+ exit 0
662
+ fi
663
+
664
+ CURRENT_DIGEST="$(printf '%s\n' "${TAG_ROWS}" | awk -v target="${{ github.sha }}" -v regex="${DIGEST_REGEX}" '
665
+ $1 == target {
666
+ for (i = 1; i <= NF; i++) {
667
+ if ($i ~ regex) {
668
+ print $i;
669
+ exit;
670
+ }
671
+ }
672
+ }
673
+ ')"
674
+
675
+ if [ -z "${CURRENT_DIGEST}" ]; then
676
+ echo "::error::Unable to determine current manifest digest for ${REPOSITORY}:${{ github.sha }}"
677
+ exit 1
678
+ fi
679
+
680
+ OLD_DIGESTS="$(printf '%s\n' "${TAG_ROWS}" | awk -v keep="${CURRENT_DIGEST}" -v regex="${DIGEST_REGEX}" '
681
+ {
682
+ for (i = 1; i <= NF; i++) {
683
+ if ($i ~ regex && $i != keep) {
684
+ print $i;
685
+ }
686
+ }
687
+ }
688
+ ' | sort -u)"
689
+
690
+ if [ -z "${OLD_DIGESTS}" ]; then
691
+ echo "No old manifests to delete for ${REPOSITORY}."
692
+ exit 0
693
+ fi
694
+
695
+ echo "Deleting old manifests from ${REPOSITORY}:"
696
+ printf '%s\n' "${OLD_DIGESTS}"
697
+
698
+ printf '%s\n' "${OLD_DIGESTS}" | while IFS= read -r DIGEST; do
699
+ if [ -z "${DIGEST}" ]; then
700
+ continue
701
+ fi
702
+
703
+ if ! printf '%s\n' "${DIGEST}" | grep -Eq "${DIGEST_REGEX}"; then
704
+ echo "Skipping invalid digest candidate: ${DIGEST}"
705
+ continue
706
+ fi
707
+
708
+ doctl registry repository delete-manifest "${REGISTRY_NAME}/${REPOSITORY}" "${DIGEST}" --force
709
+ done
710
+ <% }); %>