Merge pull request #3 from harness-community/akshit-dev

Added tests and Dockerfiles
This commit is contained in:
OP (oppenheimer)
2024-01-05 16:50:22 +05:30
committed by GitHub
8 changed files with 290 additions and 113 deletions
+66 -58
View File
@@ -1,73 +1,81 @@
# drone-push-helm-chart-docker-registry
# Introducing the _drone-push-helm-chart-docker-registry_ Plugin
Drone plugin to package and push a helm chart to a docker registry.
At Harness, we are always striving to make Continuous Integration (CI) and Continuous Deployment (CD) as smooth and efficient as possible. We understand the importance of managing and deploying Helm charts with ease. That's why we are excited to introduce the drone-push-helm-chart-docker-registry plugin, designed to simplify the packaging and pushing of Helm charts to a Docker registry.
## Build
### What is the _drone-push-helm-chart-docker-registry_ Plugin?
Run the script directly using the command:
The drone-push-helm-chart-docker-registry is a Drone plugin that streamlines the process of packaging and pushing Helm charts to a Docker registry. Helm charts are essential for deploying applications, and this plugin makes it easier than ever to integrate this step into your CI/CD pipeline.
```python
PLUGIN_CHART_NAME=CHART_NAME \
PLUGIN_CHART_VERSION=CHART_VERSION \
PLUGIN_DOCKER_REGISTRY=DOCKER_REGISTRY \
PLUGIN_CHART_PATH=CHART_PATH \
PLUGIN_DOCKER_USERNAME=DOCKER_USERNAME \
PLUGIN_DOCKER_PASSWORD=DOCKER_PASSWORD \
python3 main.py
```
### Build and Docker Image
## Docker
Building and using the plugin is a straightforward process. You can run the script directly using the following command:
Build the Docker image with the following commands:
PLUGIN_CHART_NAME=CHART_NAME \
PLUGIN_CHART_VERSION=CHART_VERSION \
PLUGIN_DOCKER_REGISTRY=DOCKER_REGISTRY \
PLUGIN_CHART_PATH=CHART_PATH \
PLUGIN_DOCKER_USERNAME=DOCKER_USERNAME \
PLUGIN_DOCKER_PASSWORD=DOCKER_PASSWORD \
bash main.sh
```
docker buildx build -t DOCKER_ORG/drone-helm-chart-docker-registry --platform linux/amd64 .
```
Additionally, you can build the Docker image with these commands:
Build the image for the Linux AMD64 platform
docker buildx build -t DOCKER_ORG/drone-helm-chart-docker-registry --platform linux/amd64 .
## Usage
This will build the image for the Linux AMD64 platform.
```bash
docker run --rm \
-e PLUGIN_CHART_NAME=${CHART_NAME} \
-e PLUGIN_CHART_VERSION=${CHART_VERSION} \
-e PLUGIN_DOCKER_REGISTRY=${DOCKER_REGISTRY} \
-e PLUGIN_CHART_PATH=${CHART_PATH} \
-e PLUGIN_DOCKER_USERNAME=${DOCKER_USERNAME} \
-e PLUGIN_DOCKER_PASSWORD=${DOCKER_PAT} \
-v $(pwd):$(pwd) \
-w $(pwd) \
harnesscommunity/drone-helm-chart-docker-registry
```
### Usage in Harness CI
In Harness CI,
Integrating the drone-push-helm-chart-docker-registry plugin into your Harness CI pipeline is easy. You can use Docker to run the plugin with environment variables. Here's how:
```yaml
- step:
type: Plugin
name: helm to docker
identifier: helm_to_docker
docker run --rm \
-e PLUGIN_CHART_NAME=${CHART_NAME} \
-e PLUGIN_CHART_VERSION=${CHART_VERSION} \
-e PLUGIN_DOCKER_REGISTRY=${DOCKER_REGISTRY} \
-e PLUGIN_CHART_PATH=${CHART_PATH} \
-e PLUGIN_DOCKER_USERNAME=${DOCKER_USERNAME} \
-e PLUGIN_DOCKER_PASSWORD=${DOCKER_PASSWORD} \
-v $(pwd):$(pwd) \
-w $(pwd) \
harnesscommunity/drone-helm-chart-docker-registry
In your Harness CI pipeline, you can define the plugin as a step, like this:
- step:
type: Plugin
name: helm to docker
identifier: helm_to_docker
spec:
connectorRef: docker-registry-connector
image: harnesscommunity/drone-helm-chart-docker-registry
settings:
chart_name: mywebapp
docker_username: <+variable.docker_username>
docker_password: <+secrets.getValue("pat-token")>
chart_path: test
chart_version: 1.0.0
docker_registry: registry.hub.docker.com
```
connectorRef: docker-registry-connector
image: harnesscommunity/drone-helm-chart-docker-registry
settings:
chart_name: mywebapp
docker_username: <+variable.docker_username>
docker_password: <+secrets.getValue("pat-token")>
chart_path: path-to-helm-chart
chart_version: 1.0.0
docker_registry: registry.hub.docker.com
```
GLOBAL OPTIONS:
--chart_name value required Helm Chart Name [$PLUGIN_CHART_NAME]
--chart_version value optional Helm Chart Version (Default: 1.0.0) [$PLUGIN_CHART_VERSION]
--docker_registry value optional Docker Registry for pushing Helm Chart (Default: registry.hub.docker.com) [$PLUGIN_DOCKER_REGISTRY]
--chart_path value optional Path to Helm Chart's Directory (Default: Root Directory) [$PLUGIN_CHART_PATH]
--docker_username value required Docker Login Username [$PLUGIN_DOCKER_USERNAME]
--docker_password value required Docker Login Password [$PLUGIN_DOCKER_PASSWORD]
```
#### Plugin Options
Please make sure the Chart name and version match the Chart.yaml, otherwise chart packaging will fail.
The plugin offers several options for customization:
- **_-\-chart_name_**: The name of your Helm chart. You should replace ${CHART_NAME} with the actual name of your Helm chart.
- **_-\-chart_version_**: This variable allows you to specify the version of your Helm chart (default: 1.0.0). You can replace ${CHART_VERSION} with the desired version.
- **_-\-docker_registry_**: Use this variable to specify the Docker registry where you want to push your Helm chart (default: registry.hub.docker.com). Replace ${DOCKER_REGISTRY} with your preferred registry.
- **_-\-chart_path_**: This variable is used to define the path to your Helm chart's directory (default: root directory). Replace ${CHART_PATH} with the actual path to your Helm chart.
- **_-\-docker_username_**: Here, you provide your Docker login username. Replace ${DOCKER_USERNAME} with your Docker username.
- **_-\-docker_password_**: This variable requires your Docker login password. Replace ${DOCKER_PASSWORD} with your Docker password.
These environment variables are essential for configuring and customizing the behavior of the drone-push-helm-chart-docker-registry plugin when it's executed as a Docker container. They allow you to provide specific values and credentials required for the plugin to package and push your Helm chart to the Docker registry of your choice. Make sure to set these variables according to your project's needs.
## Get Started with the drone-push-helm-chart-docker-registry Plugin
Whether you are a seasoned DevOps professional or just getting started with CI/CD, the drone-push-helm-chart-docker-registry plugin can streamline your deployment process and help you manage Helm charts effortlessly. Give it a try and see how it simplifies your CI/CD pipelines!
For more information, documentation, and updates, please visit our GitHub repository: [drone-push-helm-chart-docker-registry](https://github.com/harnesscommunity/drone-push-helm-chart-docker-registry).
+5
View File
@@ -0,0 +1,5 @@
apiVersion: v2
name: mywebapp
description: a simple web app
version: 5.0.0
appVersion: 5.0.0
+4 -4
View File
@@ -1,4 +1,4 @@
FROM alpine:3.18
FROM --platform=linux/amd64 alpine:3.18 as base
WORKDIR /app
@@ -11,8 +11,8 @@ RUN apk --no-cache add ca-certificates curl && \
rm -rf linux-amd64 helm-v3.7.0-linux-amd64.tar.gz && \
chmod +x /usr/local/bin/helm
RUN apk --no-cache add python3
RUN apk add --no-cache python3
COPY main.py /app/
COPY ./main.py /app/
CMD ["python3", "/app/main.py"]
CMD ["python", "/app/main.py"]
+18
View File
@@ -0,0 +1,18 @@
FROM --platform=linux/arm64 alpine:3.18 as base
WORKDIR /app
ENV HELM_EXPERIMENTAL_OCI=1
RUN apk --no-cache add ca-certificates curl && \
curl -LO "https://get.helm.sh/helm-v3.7.0-linux-arm64.tar.gz" && \
tar -zxvf helm-v3.7.0-linux-arm64.tar.gz && \
mv linux-arm64/helm /usr/local/bin/helm && \
rm -rf linux-arm64 helm-v3.7.0-linux-arm64.tar.gz && \
chmod +x /usr/local/bin/helm
RUN apk add --no-cache python3
COPY ./main.py /app/
CMD ["python", "/app/main.py"]
+20
View File
@@ -0,0 +1,20 @@
FROM --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2022
USER ContainerAdministrator
WORKDIR C:\\app
ENV HELM_EXPERIMENTAL_OCI=1
ENV chocolateyVersion=1.4.0
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
RUN Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
RUN choco install -y kubernetes-helm
RUN choco install -y python3
COPY ./main.py /app/
ENTRYPOINT ["python", "C:\\app\\main.py"]
+20
View File
@@ -0,0 +1,20 @@
FROM --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2022
USER ContainerAdministrator
WORKDIR C:\\app
ENV HELM_EXPERIMENTAL_OCI=1
ENV chocolateyVersion=1.4.0
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
RUN Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
RUN choco install -y kubernetes-helm
RUN choco install -y python3
COPY ./main.py /app/
ENTRYPOINT ["python", "C:\\app\\main.py"]
+60 -51
View File
@@ -6,59 +6,68 @@ import subprocess
# Environment Variables
# Helm Chart
CHART_NAME = os.getenv("PLUGIN_CHART_NAME") # ! required
CHART_VERSION = os.getenv("PLUGIN_CHART_VERSION", "1.0.0")
# Docker Repository Details
DOCKER_REGISTRY = os.getenv(
"PLUGIN_DOCKER_REGISTRY", 'registry.hub.docker.com') # ? optional
def main_function():
# Docker Hub Credentials
DOCKER_USERNAME = os.getenv(
"PLUGIN_DOCKER_USERNAME") # ! required
DOCKER_PASSWORD = os.getenv(
"PLUGIN_DOCKER_PASSWORD") # ! required
CHART_NAME = os.getenv("PLUGIN_CHART_NAME")
CHART_VERSION = os.getenv("PLUGIN_CHART_VERSION", "1.0.0")
# Path to Chart
CHART_PATH = os.getenv("PLUGIN_CHART_PATH") # ? optional
DOCKER_REGISTRY = os.getenv(
"PLUGIN_DOCKER_REGISTRY", 'registry.hub.docker.com')
DOCKER_USERNAME = os.getenv(
"PLUGIN_DOCKER_USERNAME")
DOCKER_PASSWORD = os.getenv(
"PLUGIN_DOCKER_PASSWORD")
CHART_PATH = os.getenv("PLUGIN_CHART_PATH")
DOCKER_NAMESPACE = os.getenv("PLUGIN_DOCKER_NAMESPACE")
if (CHART_NAME is None or CHART_NAME == ""):
print("Please provide a chart name")
exit(1)
if (DOCKER_USERNAME is None or DOCKER_PASSWORD is None or DOCKER_USERNAME == "" or DOCKER_PASSWORD == ""):
print("Please provide a username and a password")
exit(1)
if (DOCKER_NAMESPACE is None or DOCKER_NAMESPACE == ""):
print("Please provide a namespace")
exit(1)
if (CHART_PATH is not None):
os.chdir(CHART_PATH)
try:
if (subprocess.run(["helm", "package", "--dependency-update", "."]).returncode != 0):
raise Exception("Failed to package chart!")
except:
print("Failed to package chart!")
exit(1)
chart_filename = f"{CHART_NAME}-{CHART_VERSION}.tgz"
try:
login_command = ['helm', 'registry', 'login', DOCKER_REGISTRY,
'-u', DOCKER_USERNAME, '-p', DOCKER_PASSWORD]
if (subprocess.run(login_command).returncode != 0):
raise Exception("Failed to login!")
except:
print("Failed to login!")
exit(1)
try:
docker_push_command = ["helm", "push", chart_filename,
f"oci://{DOCKER_REGISTRY}/{DOCKER_NAMESPACE}"]
if (subprocess.run(docker_push_command).returncode != 0):
raise Exception("Failed to push chart!")
else:
print("Chart pushed successfully.")
except:
print("Failed to push chart!")
exit(1)
# ? validate environment variables
if (CHART_NAME is None):
print("Please provide a chart name")
exit(1)
if (DOCKER_USERNAME is None or DOCKER_PASSWORD is None):
print("Please provide a username and a password")
exit(1)
# cd into the chart directory
if (CHART_PATH is not None):
os.chdir(CHART_PATH)
# Package the helm chart
subprocess.run(["helm", "package", "--dependency-update", "."])
# Construct the chart name
chart_filename = f"{CHART_NAME}-{CHART_VERSION}.tgz"
# Login to Docker Registry
try:
login_command = ['helm', 'registry', 'login', DOCKER_REGISTRY,
'-u', DOCKER_USERNAME, '-p', DOCKER_PASSWORD]
subprocess.run(login_command)
except:
print("Failed to login!")
exit(1)
# Push the chart to Docker Hub
try:
docker_push_command = ["helm", "push", chart_filename,
f"oci://{DOCKER_REGISTRY}/{DOCKER_USERNAME}"]
subprocess.run(docker_push_command)
except:
print("Failed to push chart!")
exit(1)
if __name__ == "__main__":
main_function()
+97
View File
@@ -0,0 +1,97 @@
import unittest
from unittest.mock import patch
import os
from io import StringIO
from main import main_function
class TestPushOCIChartToRegistry(unittest.TestCase):
def tearDown(self):
os.environ["PLUGIN_CHART_NAME"] = ""
os.environ["PLUGIN_CHART_VERSION"] = ""
os.environ["PLUGIN_DOCKER_REGISTRY"] = ""
os.environ["PLUGIN_DOCKER_USERNAME"] = ""
os.environ["PLUGIN_DOCKER_PASSWORD"] = ""
os.environ["PLUGIN_CHART_PATH"] = ""
@patch("subprocess.run")
def test_successful_chart_push(self, mock_subprocess_run):
os.environ["PLUGIN_CHART_NAME"] = "mywebapp"
test_docker_username = os.environ["TEST_DOCKER_USERNAME"]
test_docker_password = os.environ["TEST_DOCKER_PASSWORD"]
os.environ["PLUGIN_DOCKER_USERNAME"] = test_docker_username
os.environ["PLUGIN_DOCKER_PASSWORD"] = test_docker_password
os.environ["PLUGIN_CHART_VERSION"] = "5.0.0"
os.environ["PLUGIN_DOCKER_REGISTRY"] = "registry.hub.docker.com"
os.environ["PLUGIN_CHART_PATH"] = "."
mock_subprocess_run.return_value.returncode = 0
with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
main_function()
mock_subprocess_run.assert_called_with(
["helm", "push", "mywebapp-5.0.0.tgz", f"oci://registry.hub.docker.com/{test_docker_username}"])
expected_output = 'Chart pushed successfully.'
self.assertEqual(mock_stdout.getvalue().strip(), expected_output)
@patch("subprocess.run")
def test_failed_to_package_chart(self, mock_subprocess_run):
os.environ["PLUGIN_CHART_NAME"] = "mywebapp"
test_docker_username = os.environ["TEST_DOCKER_USERNAME"]
test_docker_password = os.environ["TEST_DOCKER_PASSWORD"]
os.environ["PLUGIN_DOCKER_USERNAME"] = test_docker_username
os.environ["PLUGIN_DOCKER_PASSWORD"] = test_docker_password
os.environ["PLUGIN_CHART_PATH"] = "chart"
mock_subprocess_run.return_value.returncode = 1
with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
with self.assertRaises(SystemExit) as context:
main_function()
self.assertEqual(context.exception.code, 1)
expected_output = 'Failed to package chart!'
self.assertEqual(mock_stdout.getvalue().strip(), expected_output)
@patch("subprocess.run")
def test_chart_name_not_provided(self, mock_subprocess_run):
test_docker_username = os.environ["TEST_DOCKER_USERNAME"]
test_docker_password = os.environ["TEST_DOCKER_PASSWORD"]
os.environ["PLUGIN_DOCKER_USERNAME"] = test_docker_username
os.environ["PLUGIN_DOCKER_PASSWORD"] = test_docker_password
os.environ["PLUGIN_CHART_PATH"] = "chart"
mock_subprocess_run.return_value.returncode = 0
with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
with self.assertRaises(SystemExit) as context:
main_function()
self.assertEqual(context.exception.code, 1)
expected_output = 'Please provide a chart name'
self.assertEqual(mock_stdout.getvalue().strip(), expected_output)
@patch("subprocess.run")
def test_username_and_password_not_provided(self, mock_subprocess_run):
os.environ["PLUGIN_CHART_NAME"] = "mywebapp"
os.environ["PLUGIN_CHART_PATH"] = "chart"
mock_subprocess_run.return_value.returncode = 0
with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
with self.assertRaises(SystemExit) as context:
main_function()
self.assertEqual(context.exception.code, 1)
expected_output = 'Please provide a username and a password'
self.assertEqual(mock_stdout.getvalue().strip(), expected_output)
if __name__ == '__main__':
unittest.main()