summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpablos <pablos44@gmail.com>2023-05-29 15:00:47 +0300
committerGitHub <noreply@github.com>2023-05-29 15:00:47 +0300
commite92919ce1b402e024933528f99e4c13b065f19fa (patch)
treeb697426c6338e807a4500089dd6e5501f68eb9bb
parent2389c0f5bc3b959a95d507cb49503f972d37ae2e (diff)
parent2b0c4e938e3d967b0c21782fa39e4dd7b4084cec (diff)
Merge pull request #51 from robusta-dev/ci-cd-pipelines
CI/CD pipelines
-rw-r--r--.github/workflows/build-on-release.yml200
-rw-r--r--.github/workflows/docker-build-on-tag.yml43
-rw-r--r--.github/workflows/pytest-on-push.yml27
-rw-r--r--robusta_krr/api/models.py6
-rw-r--r--robusta_krr/core/models/objects.py6
-rw-r--r--tests/conftest.py81
-rw-r--r--tests/test_krr.py22
7 files changed, 368 insertions, 17 deletions
diff --git a/.github/workflows/build-on-release.yml b/.github/workflows/build-on-release.yml
new file mode 100644
index 0000000..498393f
--- /dev/null
+++ b/.github/workflows/build-on-release.yml
@@ -0,0 +1,200 @@
+name: Build and Release
+
+on:
+ release:
+ types: [created]
+
+jobs:
+ build:
+ strategy:
+ matrix:
+ os: [ubuntu-latest, windows-latest, macos-latest]
+
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Set up Python
+ uses: actions/setup-python@v2
+ with:
+ python-version: '3.9'
+
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install -r requirements.txt
+ pip install pyinstaller
+
+ - name: Install dependancies (Linux)
+ if: matrix.os == 'ubuntu-latest'
+ run: |
+ sudo apt-get install -y binutils
+
+ - name: Install the Apple certificate and provisioning profile
+ if: matrix.os == 'macos-latest'
+ env:
+ BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
+ P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
+ BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.BUILD_PROVISION_PROFILE_BASE64 }}
+ KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
+ run: |
+ # create variables
+ CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
+ PP_PATH=$RUNNER_TEMP/build_pp.mobileprovision
+ KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
+
+ # import certificate and provisioning profile from secrets
+ echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH
+ echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode -o $PP_PATH
+
+ # create temporary keychain
+ security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
+ security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
+ security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
+
+ # import certificate to keychain
+ security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
+ security list-keychain -d user -s $KEYCHAIN_PATH
+
+ # apply provisioning profile
+ mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
+ cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles
+
+ - name: Set version in code (Unix)
+ if: matrix.os == 'macos-latest' || matrix.os == 'ubuntu-latest'
+ run: |
+ awk 'NR==3{$0="__version__ = \"'${{ github.ref_name }}'\""}1' ./robusta_krr/__init__.py > temp && mv temp ./robusta_krr/__init__.py
+ cat ./robusta_krr/__init__.py
+
+ - name: Set version in code (Windows)
+ if: matrix.os == 'windows-latest'
+ run: |
+ $content = Get-Content -Path .\robusta_krr\__init__.py
+ $content[2] = "__version__=`"$($env:GITHUB_REF_NAME)`""
+ $content | Out-File -FilePath .\robusta_krr\__init__.py -Encoding ascii
+ Get-Content .\robusta_krr\__init__.py
+ shell: pwsh
+ env:
+ GITHUB_REF_NAME: ${{ github.ref_name }}
+
+
+ - name: Build with PyInstaller
+ shell: bash
+ run: |
+ pyinstaller krr.py
+ mkdir -p ./dist/krr/grapheme/data
+ cp $(python -c "import grapheme; print(grapheme.__path__[0] + '/data/grapheme_break_property.json')") ./dist/krr/grapheme/data/grapheme_break_property.json
+
+ - name: Zip the application (Unix)
+ if: matrix.os == 'macos-latest' || matrix.os == 'ubuntu-latest'
+ run: |
+ cd dist
+ zip -r krr-${{ matrix.os }}-${{ github.ref_name }}.zip krr
+ mv krr-${{ matrix.os }}-${{ github.ref_name }}.zip ../
+ cd ..
+
+ - name: Zip the application (Windows)
+ if: matrix.os == 'windows-latest'
+ run: |
+ Set-Location -Path dist
+ Compress-Archive -Path krr -DestinationPath krr-${{ matrix.os }}-${{ github.ref_name }}.zip -Force
+ Move-Item -Path krr-${{ matrix.os }}-${{ github.ref_name }}.zip -Destination ..\
+ Set-Location -Path ..
+
+ - name: Upload Release Asset
+ uses: actions/upload-release-asset@v1.0.2
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ upload_url: ${{ github.event.release.upload_url }}
+ asset_path: ./krr-${{ matrix.os }}-${{ github.ref_name }}.zip
+ asset_name: krr-${{ matrix.os }}-${{ github.ref_name }}.zip
+ asset_content_type: application/octet-stream
+
+ - name: Upload build as artifact
+ uses: actions/upload-artifact@v2
+ with:
+ name: krr-${{ matrix.os }}-${{ github.ref_name }}
+ path: ./krr-${{ matrix.os }}-${{ github.ref_name }}.zip
+
+ - name: Clean up keychain and provisioning profile
+ if: (matrix.os == 'macos-latest') && always()
+ run: |
+ security delete-keychain $RUNNER_TEMP/app-signing.keychain-db
+ rm ~/Library/MobileDevice/Provisioning\ Profiles/build_pp.mobileprovision
+
+ check-latest:
+ needs: build
+ runs-on: ubuntu-latest
+ outputs:
+ IS_LATEST: ${{ steps.check-latest.outputs.release == github.ref_name }}
+ steps:
+ - id: check-latest
+ uses: pozetroninc/github-action-get-latest-release@v0.7.0
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ repository: ${{ github.repository }}
+ excludes: prerelease, draft
+
+ # Define MacOS hash job
+ mac-hash:
+ needs: check-latest
+ runs-on: ubuntu-latest
+ if: needs.check-latest.outputs.IS_LATEST
+ outputs:
+ MAC_BUILD_HASH: ${{ steps.calc-hash.outputs.MAC_BUILD_HASH }}
+ steps:
+ - name: Checkout Repository
+ uses: actions/checkout@v2
+ - name: Download MacOS artifact
+ uses: actions/download-artifact@v2
+ with:
+ name: krr-macos-latest-${{ github.ref_name }}
+ - name: Calculate hash
+ id: calc-hash
+ run: echo "::set-output name=MAC_BUILD_HASH::$(sha256sum krr-macos-latest-${{ github.ref_name }}.zip | awk '{print $1}')"
+
+ # Define Linux hash job
+ linux-hash:
+ needs: check-latest
+ runs-on: ubuntu-latest
+ if: needs.check-latest.outputs.IS_LATEST
+ outputs:
+ LINUX_BUILD_HASH: ${{ steps.calc-hash.outputs.LINUX_BUILD_HASH }}
+ steps:
+ - name: Checkout Repository
+ uses: actions/checkout@v2
+ - name: Download Linux artifact
+ uses: actions/download-artifact@v2
+ with:
+ name: krr-ubuntu-latest-${{ github.ref_name }}
+ - name: Calculate hash
+ id: calc-hash
+ run: echo "::set-output name=LINUX_BUILD_HASH::$(sha256sum krr-ubuntu-latest-${{ github.ref_name }}.zip | awk '{print $1}')"
+
+ # Define job to update homebrew formula
+ update-formula:
+ needs: [mac-hash, linux-hash]
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout homebrew-krr repository
+ uses: actions/checkout@v2
+ with:
+ repository: robusta-dev/homebrew-krr
+ token: ${{ secrets.MULTIREPO_GITHUB_TOKEN }}
+ - name: Update krr.rb formula
+ run: |
+ MAC_BUILD_HASH=${{ needs.mac-hash.outputs.MAC_BUILD_HASH }}
+ LINUX_BUILD_HASH=${{ needs.linux-hash.outputs.LINUX_BUILD_HASH }}
+ TAG_NAME=${{ github.ref_name }}
+ awk 'NR==6{$0=" url \"https://github.com/robusta-dev/krr/releases/download/'"$TAG_NAME"'/krr-macos-latest-'"$TAG_NAME"'.zip\""}1' ./Formula/krr.rb > temp && mv temp ./Formula/krr.rb
+ awk 'NR==7{$0=" sha256 \"'$MAC_BUILD_HASH'\""}1' ./Formula/krr.rb > temp && mv temp ./Formula/krr.rb
+ awk 'NR==9{$0=" url \"https://github.com/robusta-dev/krr/releases/download/'"$TAG_NAME"'/krr-linux-latest-'"$TAG_NAME"'.zip\""}1' ./Formula/krr.rb > temp && mv temp ./Formula/krr.rb
+ awk 'NR==10{$0=" sha256 \"'$LINUX_BUILD_HASH'\""}1' ./Formula/krr.rb > temp && mv temp ./Formula/krr.rb
+ - name: Commit and push changes
+ run: |
+ git config --local user.email "action@github.com"
+ git config --local user.name "GitHub Action"
+ git commit -am "Update formula for release ${TAG_NAME}"
+ git push
diff --git a/.github/workflows/docker-build-on-tag.yml b/.github/workflows/docker-build-on-tag.yml
new file mode 100644
index 0000000..97829f5
--- /dev/null
+++ b/.github/workflows/docker-build-on-tag.yml
@@ -0,0 +1,43 @@
+name: Docker Build and Push
+
+on:
+ push:
+ tags:
+ - '*'
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Set up gcloud CLI
+ uses: google-github-actions/setup-gcloud@v0.2.0
+ with:
+ service_account_key: ${{ secrets.GCP_SA_KEY }}
+ project_id: genuine-flight-317411
+ export_default_credentials: true
+
+ # Configure Docker to use the gcloud command-line tool as a credential helper for authentication
+ - name: Configure Docker
+ run: |-
+ gcloud auth configure-docker us-central1-docker.pkg.dev
+
+ - name: Verify gcloud configuration
+ run: |-
+ gcloud config get-value project
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v1
+
+ - name: Build and push Docker images
+ uses: docker/build-push-action@v2
+ with:
+ context: .
+ platforms: linux/arm64,linux/amd64
+ push: true
+ tags: us-central1-docker.pkg.dev/genuine-flight-317411/devel/krr:${{ github.ref_name }}
+ build-args: |
+ BUILDKIT_INLINE_CACHE=1
diff --git a/.github/workflows/pytest-on-push.yml b/.github/workflows/pytest-on-push.yml
new file mode 100644
index 0000000..a867168
--- /dev/null
+++ b/.github/workflows/pytest-on-push.yml
@@ -0,0 +1,27 @@
+name: Pytest
+
+on: [push]
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Set up Python
+ uses: actions/setup-python@v2
+ with:
+ python-version: '3.9'
+
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install -r requirements.txt
+ pip install -e .
+ pip install pytest
+
+ - name: Test with pytest
+ run: |
+ pytest
diff --git a/robusta_krr/api/models.py b/robusta_krr/api/models.py
index be2a3ad..a0c4ea9 100644
--- a/robusta_krr/api/models.py
+++ b/robusta_krr/api/models.py
@@ -1,6 +1,6 @@
-from robusta_krr.core.abstract.strategies import HistoryData, ResourceRecommendation, RunResult
+from robusta_krr.core.abstract.strategies import HistoryData, ResourceHistoryData, ResourceRecommendation, RunResult
from robusta_krr.core.models.allocations import RecommendationValue, ResourceAllocations, ResourceType
-from robusta_krr.core.models.objects import K8sObjectData
+from robusta_krr.core.models.objects import K8sObjectData, PodData
from robusta_krr.core.models.result import ResourceScan, Result, Severity
__all__ = [
@@ -8,10 +8,12 @@ __all__ = [
"ResourceAllocations",
"RecommendationValue",
"K8sObjectData",
+ "PodData",
"Result",
"Severity",
"ResourceScan",
"ResourceRecommendation",
"HistoryData",
+ "ResourceHistoryData",
"RunResult",
]
diff --git a/robusta_krr/core/models/objects.py b/robusta_krr/core/models/objects.py
index 21f5d61..84e468e 100644
--- a/robusta_krr/core/models/objects.py
+++ b/robusta_krr/core/models/objects.py
@@ -1,5 +1,3 @@
-from typing import Optional
-
import pydantic as pd
from robusta_krr.core.models.allocations import ResourceAllocations
@@ -14,12 +12,12 @@ class PodData(pd.BaseModel):
class K8sObjectData(pd.BaseModel):
- cluster: Optional[str]
+ cluster: str
name: str
container: str
pods: list[PodData]
namespace: str
- kind: Optional[str]
+ kind: str
allocations: ResourceAllocations
def __str__(self) -> str:
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000..0617ddc
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,81 @@
+import random
+from datetime import datetime, timedelta
+from unittest.mock import AsyncMock, PropertyMock, patch
+
+import numpy as np
+import pytest
+
+from robusta_krr.api.models import K8sObjectData, PodData, ResourceAllocations, ResourceHistoryData
+
+TEST_OBJECT = K8sObjectData(
+ cluster="mock-cluster",
+ name="mock-object-1",
+ container="mock-container-1",
+ pods=[
+ PodData(name="mock-pod-1", deleted=False),
+ PodData(name="mock-pod-2", deleted=False),
+ PodData(name="mock-pod-3", deleted=True),
+ ],
+ namespace="default",
+ kind="Deployment",
+ allocations=ResourceAllocations(
+ requests={"cpu": 1, "memory": 1}, # type: ignore
+ limits={"cpu": 2, "memory": 2}, # type: ignore
+ ),
+)
+
+
+@pytest.fixture(autouse=True, scope="session")
+def mock_list_clusters():
+ with patch(
+ "robusta_krr.core.integrations.kubernetes.KubernetesLoader.list_clusters",
+ new=AsyncMock(return_value=[TEST_OBJECT.cluster]),
+ ):
+ yield
+
+
+@pytest.fixture(autouse=True, scope="session")
+def mock_list_scannable_objects():
+ with patch(
+ "robusta_krr.core.integrations.kubernetes.KubernetesLoader.list_scannable_objects",
+ new=AsyncMock(return_value=[TEST_OBJECT]),
+ ):
+ yield
+
+
+@pytest.fixture(autouse=True, scope="session")
+def mock_config_loaded():
+ with patch("robusta_krr.core.models.config.Config.config_loaded", new_callable=PropertyMock) as mock_config:
+ mock_config.return_value = True
+ yield
+
+
+@pytest.fixture(autouse=True, scope="session")
+def mock_prometheus_loader():
+ now = datetime.now()
+ start = now - timedelta(hours=1)
+ now_ts, start_ts = now.timestamp(), start.timestamp()
+ metric_points_data = np.array([(t, random.randrange(0, 100)) for t in np.linspace(start_ts, now_ts, 3600)])
+
+ with patch(
+ "robusta_krr.core.integrations.prometheus.loader.PrometheusLoader.gather_data",
+ new=AsyncMock(
+ return_value=ResourceHistoryData(
+ data={pod.name: metric_points_data for pod in TEST_OBJECT.pods},
+ metric={ # type: ignore
+ "query": f"example_promql_metric{{pod_name=~\"{'|'.join(pod.name for pod in TEST_OBJECT.pods)}\"}}",
+ "start_time": start,
+ "end_time": now,
+ "step": "30s",
+ },
+ )
+ ),
+ ) as mock_prometheus_loader:
+ mock_prometheus_loader
+ yield
+
+
+@pytest.fixture(autouse=True, scope="session")
+def mock_prometheus_init():
+ with patch("robusta_krr.core.integrations.prometheus.loader.PrometheusLoader.__init__", return_value=None):
+ yield
diff --git a/tests/test_krr.py b/tests/test_krr.py
index 23dab01..f013ee4 100644
--- a/tests/test_krr.py
+++ b/tests/test_krr.py
@@ -1,8 +1,3 @@
-"""
- Test the krr command line interface and a general execution.
- Requires a running kubernetes cluster with the kubectl command configured.
-"""
-
import json
import pytest
@@ -36,14 +31,19 @@ def test_run(log_flag: str):
@pytest.mark.parametrize("format", ["json", "yaml", "table", "pprint"])
def test_output_formats(format: str):
- result = runner.invoke(app, [STRATEGY_NAME, "-q", "-f", format, "--namespace", "default"])
+ result = runner.invoke(app, [STRATEGY_NAME, "-q", "-f", format])
try:
assert result.exit_code == 0, result.exc_info
except AssertionError as e:
raise e from result.exception
- if format == "json":
- assert json.loads(result.stdout), result.stdout
-
- if format == "yaml":
- assert yaml.safe_load(result.stdout), result.stdout
+ try:
+ if format == "json":
+ json_output = json.loads(result.stdout)
+ assert json_output, result.stdout
+ assert len(json_output["scans"]) > 0, result.stdout
+
+ if format == "yaml":
+ assert yaml.safe_load(result.stdout), result.stdout
+ except Exception as e:
+ raise Exception(result.stdout) from e