mirror of
https://github.com/harness-community/drone-helm-chart-container-registry.git
synced 2026-06-04 18:24:12 +08:00
migrated to go, add GAR support
This commit is contained in:
@@ -1,18 +1,14 @@
|
||||
FROM --platform=linux/amd64 alpine:3.18 as base
|
||||
FROM --platform=linux/amd64 alpine:latest 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-amd64.tar.gz" && \
|
||||
tar -zxvf helm-v3.7.0-linux-amd64.tar.gz && \
|
||||
curl -LO "https://get.helm.sh/helm-v3.8.0-linux-amd64.tar.gz" && \
|
||||
tar -zxvf helm-v3.8.0-linux-amd64.tar.gz && \
|
||||
mv linux-amd64/helm /usr/local/bin/helm && \
|
||||
rm -rf linux-amd64 helm-v3.7.0-linux-amd64.tar.gz && \
|
||||
rm -rf linux-amd64 helm-v3.8.0-linux-amd64.tar.gz && \
|
||||
chmod +x /usr/local/bin/helm
|
||||
|
||||
RUN apk add --no-cache python3
|
||||
COPY ./drone-helm /app/
|
||||
|
||||
COPY ./main.py /app/
|
||||
|
||||
CMD ["python", "/app/main.py"]
|
||||
CMD ["/app/drone-helm"]
|
||||
@@ -1,18 +1,14 @@
|
||||
FROM --platform=linux/arm64 alpine:3.18 as base
|
||||
FROM --platform=linux/arm64 alpine:latest 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 && \
|
||||
curl -LO "https://get.helm.sh/helm-v3.8.0-linux-amd64.tar.gz" && \
|
||||
tar -zxvf helm-v3.8.0-linux-amd64.tar.gz && \
|
||||
mv linux-amd64/helm /usr/local/bin/helm && \
|
||||
rm -rf linux-amd64 helm-v3.8.0-linux-amd64.tar.gz && \
|
||||
chmod +x /usr/local/bin/helm
|
||||
|
||||
RUN apk add --no-cache python3
|
||||
COPY ./drone-helm /app/
|
||||
|
||||
COPY ./main.py /app/
|
||||
|
||||
CMD ["python", "/app/main.py"]
|
||||
CMD ["/app/drone-helm"]
|
||||
@@ -13,8 +13,6 @@ RUN Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointMa
|
||||
|
||||
RUN choco install -y kubernetes-helm
|
||||
|
||||
RUN choco install -y python3
|
||||
COPY ./drone-helm.exe /app/
|
||||
|
||||
COPY ./main.py /app/
|
||||
|
||||
ENTRYPOINT ["python", "C:\\app\\main.py"]
|
||||
CMD ["C:\\app\\drone-helm.exe"]
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
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"]
|
||||
@@ -0,0 +1,140 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// get environment variables
|
||||
registry := os.Getenv("PLUGIN_REGISTRY")
|
||||
username := os.Getenv("PLUGIN_USERNAME")
|
||||
token := os.Getenv("PLUGIN_TOKEN")
|
||||
chartPath := os.Getenv("PLUGIN_CHART_PATH")
|
||||
namespace := os.Getenv("PLUGIN_NAMESPACE")
|
||||
|
||||
if (registry == "") || (username == "") || (token == "") || (namespace == "") {
|
||||
fmt.Println("Missing required environment variables")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if chartPath == "" {
|
||||
chartPath = "./"
|
||||
}
|
||||
|
||||
// get chart name
|
||||
chartName, err := getChartName(chartPath)
|
||||
if err != nil {
|
||||
fmt.Println("Failed to get chart name")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
chartName = strings.TrimSpace(chartName)
|
||||
|
||||
// get chart version
|
||||
chartVersion, err := getChartVersion(chartPath)
|
||||
if err != nil {
|
||||
fmt.Println("Failed to get chart version")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
chartVersion = strings.TrimSpace(chartVersion)
|
||||
|
||||
// cd into chart path
|
||||
if err := os.Chdir(chartPath); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// package chart
|
||||
packageChartCmd := exec.Command("helm", "package", "--dependency-update", ".")
|
||||
if err := packageChartCmd.Run(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// get chart tarball name
|
||||
chartTarballName := fmt.Sprintf("%s-%s.tgz", chartName, chartVersion)
|
||||
|
||||
fmt.Println("Chart packaged successfully - ", chartTarballName)
|
||||
|
||||
// login to registry
|
||||
loginCmd := exec.Command("helm", "registry", "login", registry, "--username", username, "--password", token)
|
||||
if err := loginCmd.Run(); err != nil {
|
||||
fmt.Println("Failed to login to registry")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println("Logged in to registry successfully")
|
||||
|
||||
// push chart
|
||||
pushChartCmd := exec.Command("helm", "push", chartTarballName, fmt.Sprintf("oci://%s/%s", registry, namespace))
|
||||
if err := pushChartCmd.Run(); err != nil {
|
||||
fmt.Println("Failed to push chart")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println("Chart pushed successfully - ", registry, "/", namespace, "/", chartName)
|
||||
}
|
||||
|
||||
func getChartName(chartPath string) (string, error) {
|
||||
// helm show chart chartPath | grep name | awk '{print $2}'
|
||||
cmd1 := exec.Command("helm", "show", "chart", chartPath)
|
||||
cmd2 := exec.Command("awk", "/name:/ {print $2}")
|
||||
|
||||
// if os is windows
|
||||
if runtime.GOOS == "windows" {
|
||||
cmd1 = exec.Command("helm", "show", "chart", chartPath)
|
||||
cmd2 = exec.Command("powershell", "-Command", "Select-String 'name:' | ForEach-Object { $_.Matches.Groups[1].Value }")
|
||||
}
|
||||
|
||||
var out1 bytes.Buffer
|
||||
var out2 bytes.Buffer
|
||||
|
||||
cmd1.Stdout = &out1
|
||||
cmd2.Stdin = &out1
|
||||
cmd2.Stdout = &out2
|
||||
|
||||
if err := cmd1.Run(); err != nil {
|
||||
fmt.Println("Chart does not exist in the specified path")
|
||||
return "", err
|
||||
}
|
||||
if err := cmd2.Run(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return out2.String(), nil
|
||||
}
|
||||
|
||||
func getChartVersion(chartPath string) (string, error) {
|
||||
// helm show chart chartPath | grep version | awk '{print $2}'
|
||||
cmd1 := exec.Command("helm", "show", "chart", chartPath)
|
||||
cmd2 := exec.Command("awk", "/version:/ {print $2}")
|
||||
|
||||
// if os is windows
|
||||
if runtime.GOOS == "windows" {
|
||||
cmd1 = exec.Command("helm", "show", "chart", chartPath)
|
||||
cmd2 = exec.Command("powershell", "-Command", "Select-String 'version:' | ForEach-Object { $_.Matches.Groups[1].Value }")
|
||||
}
|
||||
|
||||
var out1 bytes.Buffer
|
||||
var out2 bytes.Buffer
|
||||
|
||||
cmd1.Stdout = &out1
|
||||
cmd2.Stdin = &out1
|
||||
cmd2.Stdout = &out2
|
||||
|
||||
if err := cmd1.Run(); err != nil {
|
||||
fmt.Println("Chart does not exist in the specified path")
|
||||
return "", err
|
||||
}
|
||||
if err := cmd2.Run(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return out2.String(), nil
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
# Plugin Name: Push OCI Chart to Registry
|
||||
# Description: Pushes an Helm Chart to a Docker Registry
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
# Environment Variables
|
||||
|
||||
|
||||
def main_function():
|
||||
|
||||
CHART_NAME = os.getenv("PLUGIN_CHART_NAME")
|
||||
CHART_VERSION = os.getenv("PLUGIN_CHART_VERSION", "1.0.0")
|
||||
|
||||
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)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main_function()
|
||||
@@ -1,97 +0,0 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user