Rewrite in Zig
continuous-integration/drone/push Build is passing Details

master
Clément FRÉVILLE 2 years ago
parent 5028f127da
commit fa62c57585

@ -13,7 +13,7 @@ steps:
dockerfile: Dockerfile dockerfile: Dockerfile
context: . context: .
registry: hub.codefirst.iut.uca.fr registry: hub.codefirst.iut.uca.fr
repo: hub.codefirst.iut.uca.fr/thomas.bellembois/codefirst-dockerproxy-clientdrone repo: hub.codefirst.iut.uca.fr/clement.freville2/codefirst-dockerproxy-clientdrone
username: username:
from_secret: SECRET_REGISTRY_USERNAME from_secret: SECRET_REGISTRY_USERNAME
password: password:

7
.gitignore vendored

@ -21,3 +21,10 @@
# Go workspace file # Go workspace file
go.work go.work
# IDE
.vscode
.idea
# Zig
zig-cache/
zig-out/

@ -1,102 +0,0 @@
# options for analysis running
run:
# timeout for analysis, e.g. 30s, 5m, default is 1m
timeout: 1m
# exit code when at least one issue was found, default is 1
issues-exit-code: 0
# include test files or not, default is true
tests: false
# which dirs to skip: issues from them won't be reported;
# can use regexp here: generated.*, regexp is applied on full path;
# default value is empty list, but default dirs are skipped independently
# from this option's value (see skip-dirs-use-default).
# "/" will be replaced by current OS file path separator to properly work
# on Windows.
skip-dirs:
- wasm
- static
- node_modules
- documents
- docker
- bind-*
# default is true. Enables skipping of directories:
# vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
skip-dirs-use-default: true
# by default isn't set. If set we pass it to "go list -mod={option}". From "go help modules":
# If invoked with -mod=readonly, the go command is disallowed from the implicit
# automatic updating of go.mod described above. Instead, it fails when any changes
# to go.mod are needed. This setting is most useful to check that go.mod does
# not need updates, such as in a continuous integration and testing system.
# If invoked with -mod=vendor, the go command assumes that the vendor
# directory holds the correct copies of dependencies and ignores
# the dependency descriptions in go.mod.
modules-download-mode: mod
# Allow multiple parallel golangci-lint instances running.
# If false (default) - golangci-lint acquires file lock on start.
allow-parallel-runners: true
# output configuration options
output:
# colored-line-number|line-number|json|tab|checkstyle|code-climate|junit-xml|github-actions
# default is "colored-line-number"
format: colored-line-number
# print lines of code with issue, default is true
print-issued-lines: true
# print linter name in the end of issue text, default is true
print-linter-name: true
# make issues output unique by line, default is true
uniq-by-line: true
# add a prefix to the output file references; default is no prefix
path-prefix: ""
# sorts results by: filepath, line and column
sort-results: false
# all available settings of specific linters
linters-settings:
gofmt:
# simplify code: gofmt with `-s` option, true by default
simplify: true
staticcheck:
# Select the Go version to target. The default is '1.13'.
go: "1.19"
# https://staticcheck.io/docs/options#checks
checks: ["all"]
stylecheck:
# Select the Go version to target. The default is '1.13'.
go: "1.19"
# https://staticcheck.io/docs/options#checks
checks: ["all"]
errcheck:
# report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`;
# default is false: such cases aren't reported by default.
check-blank: true
linters:
enable:
# go install github.com/kisielk/errcheck@latest
# go install github.com/gordonklaus/ineffassign@latest
# go install honnef.co/go/tools/cmd/staticcheck@latest
# go install gitlab.com/opennota/check/cmd/varcheck@latest
# go install github.com/go-critic/go-critic/cmd/gocritic@latest
- errcheck
- staticcheck
- stylecheck
- ineffassign
- varcheck
- gofmt
- gocritic
- wsl
fast: false

@ -1,18 +1,13 @@
FROM golang:1.19-bullseye FROM docker.io/alpine:3.18 AS builder
LABEL author="Thomas Bellembois"
# Copying sources. RUN apk add --no-cache zig --repository=https://dl-cdn.alpinelinux.org/alpine/edge/testing
WORKDIR /go/src/codefirst.iut.uca.fr/git/thomas.bellembois/codefirst-dockerproxy-clientdrone/v2/
COPY . .
# Installing. WORKDIR /work
RUN go install -v ./... COPY src src
RUN chmod +x /go/bin/codefirst-dockerproxy-clientdrone COPY build.zig build.zig
# Copying entrypoint. RUN zig build -Doptimize=ReleaseSafe
COPY entrypoint.sh /
RUN chmod +x /entrypoint.sh
USER root FROM scratch AS runner
EXPOSE 8081 COPY --from=builder /work/zig-out/bin/codefirst-dockerproxy-clientdrone /
ENTRYPOINT [ "/entrypoint.sh" ] ENTRYPOINT [ "/codefirst-dockerproxy-clientdrone" ]

@ -1,27 +1,18 @@
sur le proxy: # codefirst-dockerproxy-clientdrone
```
docker network create cicd_net
./zig-out/bin/codefirst-dockerproxy -d
```
sur le client web:
```
# Ajuster la constante proxyUrl et proxyPath puis compiler.
docker run --rm -it --volume $(pwd):/app sandrokeil/typescript tsc /app/src/index.ts
mv src/index.js build/src/index.js
# Build l'image. Usage:
docker build -t hub.codefirst.iut.uca.fr/thomas.bellembois/codefirst-dockerproxy-clientweb .
# Run le container. ```yaml
docker run -p 8081:80 hub.codefirst.iut.uca.fr/thomas.bellembois/codefirst-dockerproxy-clientweb - name: deploy-container
image: hub.codefirst.iut.uca.fr/clement.freville2/codefirst-dockerproxy-clientdrone:latest
settings:
image: hub.codefirst.iut.uca.fr/my.login/my_repository:latest
container: my_container_name
command: create
overwrite: true
``` ```
-> http://localhost:8081/dockerrunner/ ## Changes from upstream
sur le client cd: - Removed the shell script to handle spaces in arguments.
``` - Used Drone settings instead of environment variables.
go run . -proxyhost localhost:8080 -devel -command create -imagename nginx -containername nginx1 -admins john.doe,mickey.mouse
go run . -proxyhost localhost:8080 -devel -command create -imagename nginx -containername nginx2
go run . -proxyhost localhost:8080 -devel -command create -imagename nginx -containername nginx3
```

@ -0,0 +1,55 @@
const std = @import("std");
// Although this function looks imperative, note that its job is to
// declaratively construct a build graph that will be executed by an external
// runner.
pub fn build(b: *std.Build) void {
// Standard target options allows the person running `zig build` to choose
// what target to build for. Here we do not override the defaults, which
// means any target is allowed, and the default is native. Other options
// for restricting supported target set are available.
const target = b.standardTargetOptions(.{});
// Standard optimization options allow the person running `zig build` to select
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
// set a preferred release mode, allowing the user to decide how to optimize.
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "codefirst-dockerproxy-clientdrone",
// In this case the main source file is merely a path, however, in more
// complicated build scripts, this could be a generated file.
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
});
exe.strip = true;
// This declares intent for the executable to be installed into the
// standard location when the user invokes the "install" step (the default
// step when running `zig build`).
b.installArtifact(exe);
// This *creates* a Run step in the build graph, to be executed when another
// step is evaluated that depends on it. The next line below will establish
// such a dependency.
const run_cmd = b.addRunArtifact(exe);
// By making the run step depend on the install step, it will be run from the
// installation directory rather than directly from within the cache directory.
// This is not necessary, however, if the application depends on other installed
// files, this ensures they will be present and in the expected location.
run_cmd.step.dependOn(b.getInstallStep());
// This allows the user to pass arguments to the application in the build
// command itself, like this: `zig build run -- arg1 arg2 etc`
if (b.args) |args| {
run_cmd.addArgs(args);
}
// This creates a build step. It will be visible in the `zig build --help` menu,
// and can be selected like this: `zig build run`
// This will evaluate the `run` step rather than the default, which is "install".
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
}

@ -1,88 +0,0 @@
#!/usr/bin/env bash
ProxyScheme=""
ProxyHost=""
ProxyPath=""
ImageName=""
ContainerName=""
Overwrite=""
Private=""
Env=""
Command=""
Admins=""
if [ ! -z "$PROXYSCHEME" ]
then
ProxyScheme="-proxyscheme $PROXYSCHEME"
fi
if [ ! -z "$PROXYHOST" ]
then
ProxyHost="-proxyhost $PROXYHOST"
fi
if [ ! -z "$PROXYPATH" ]
then
ProxyPath="-proxypath $PROXYPATH"
fi
if [ ! -z "$IMAGENAME" ]
then
ImageName="-imagename $IMAGENAME"
fi
if [ ! -z "$CONTAINERNAME" ]
then
ContainerName="-containername $CONTAINERNAME"
fi
if [ ! -z "$COMMAND" ]
then
Command="-command $COMMAND"
fi
if [ ! -z "$ADMINS" ]
then
Admins="-admins $ADMINS"
fi
if [ ! -z "$PRIVATE" ]
then
Private="-private"
fi
if [ ! -z "$OVERWRITE" ]
then
Overwrite="-overwrite"
fi
prefix="CODEFIRST_CLIENTDRONE_ENV_"
ENVS=$(env | awk -F "=" '{print $1}' | grep ".*$prefix.*")
if [ ! -z "$ENVS" ]
then
Env=""
arrayEnv=($ENVS)
for i in "${arrayEnv[@]}"
do
envVarName=${i#"$prefix"}
Env=$Env" -env $envVarName=${!i}"
done
fi
echo $ProxyScheme
echo $ProxyHost
echo $ProxyPath
echo $ImageName
echo $ContainerName
echo $Overwrite
echo $Private
echo $Admins
echo $Env
echo $Command
#/go/bin
sh -c "/go/bin/codefirst-dockerproxy-clientdrone $ProxyScheme $ProxyHost $ProxyPath $ImageName $ContainerName $Private $Admins $Overwrite $Env $Command"

@ -1,7 +0,0 @@
module codefirst.iut.uca.fr/git/thomas.bellembois/codefirst-dockerproxy-clientdrone
go 1.19
require github.com/go-resty/resty/v2 v2.7.0
require golang.org/x/net v0.0.0-20211029224645-99673261e6eb // indirect

@ -1,9 +0,0 @@
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
golang.org/x/net v0.0.0-20211029224645-99673261e6eb h1:pirldcYWx7rx7kE5r+9WsOXPXK0+WH5+uZ7uPmJ44uM=
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

@ -1,267 +0,0 @@
package main
import (
"flag"
"fmt"
"os"
"strings"
"github.com/go-resty/resty/v2"
)
type CodeFirstContainer struct {
ID string `json:"Id"`
Image string `json:"Image"`
Env []string `json:"Env,omitempty"`
Admins string `json:"Admins,omitempty"`
Private bool `json:"Private,omitempty"`
}
type StringSliceFlag struct {
value []string
}
func (s *StringSliceFlag) String() string {
return fmt.Sprintf("%s", *s)
}
func (s *StringSliceFlag) Set(v string) error {
s.value = append(s.value, v)
return nil
}
var (
authUser string
command string
proxyScheme, proxyHost, proxyPath string
imageName, containerName, admins string
private, overwrite, devel bool
env StringSliceFlag
)
func main() {
env = StringSliceFlag{}
flag.StringVar(&command, "command", "list", "list|logs|create|delete|start")
flag.BoolVar(&devel, "devel", false, "use fake x-forwarded-user")
flag.StringVar(&proxyScheme, "proxyscheme", "http", "proxy scheme")
flag.StringVar(&proxyHost, "proxyhost", "dockerproxy:8080", "proxy host")
flag.StringVar(&proxyPath, "proxypath", "/", "proxy path")
flag.StringVar(&imageName, "imagename", "", "image name")
flag.StringVar(&containerName, "containername", "", "container name")
flag.StringVar(&admins, "admins", "", "admins (comma separated list)")
flag.BoolVar(&private, "private", false, "private container")
flag.BoolVar(&overwrite, "overwrite", false, "overwrite existing container")
flag.Var(&env, "env", "environment variables (separated by spaces)")
flag.Parse()
fmt.Println("flags:")
fmt.Printf("-imagename: %s\n", imageName)
fmt.Printf("-containername: %s\n", containerName)
fmt.Printf("-private: %t\n", private)
fmt.Printf("-admins: %s\n", admins)
fmt.Printf("-overwrite: %t\n", overwrite)
fmt.Printf("-env: %s\n", env)
if command != "list" && containerName == "" {
fmt.Println("Missing containername parameter.")
os.Exit(1)
}
if command == "create" && imageName == "" {
fmt.Println("Missing imagename parameter.")
os.Exit(1)
}
if devel {
authUser = "thomas.bellembois"
} else {
authUser = os.Getenv("DRONE_REPO_OWNER")
}
containerName = authUser + "-" + containerName
containerName = strings.ReplaceAll(containerName, ".", "")
fmt.Printf("authUser: %s\n", authUser)
fmt.Printf("new containerName: %s\n", containerName)
if len(authUser) == 0 {
fmt.Println("Not authenticated.")
os.Exit(1)
}
switch command {
case "list":
list()
case "logs":
logs()
case "create":
if overwrite {
delete(true)
}
if !exist() {
createImage()
create()
start()
}
case "start":
start()
case "delete":
delete(false)
}
}
func exist() bool {
client := resty.New()
resp, err := client.R().
SetHeader("x-forwarded-user", authUser).
Get(fmt.Sprintf("%s://%s/containers/%s/json", proxyScheme, proxyHost, containerName))
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Println(string(resp.Body()))
return resp.IsSuccess()
}
func list() {
client := resty.New()
resp, err := client.R().
SetHeader("x-forwarded-user", authUser).
Get(fmt.Sprintf("%s://%s/containers/json", proxyScheme, proxyHost))
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Println(string(resp.Body()))
if resp.IsError() {
os.Exit(1)
}
}
func logs() {
client := resty.New()
resp, err := client.R().
SetHeader("x-forwarded-user", authUser).
Get(fmt.Sprintf("%s://%s/containers/%s/logs", proxyScheme, proxyHost, containerName))
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Println(string(resp.Body()))
if resp.IsError() {
os.Exit(1)
}
}
func start() {
client := resty.New()
container := CodeFirstContainer{
Image: imageName,
Env: env.value,
Private: private,
}
resp, err := client.R().
SetHeader("x-forwarded-user", authUser).
SetBody(container).
Post(fmt.Sprintf("%s://%s/containers/%s/start", proxyScheme, proxyHost, containerName))
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Println(string(resp.Body()))
if resp.IsError() {
os.Exit(1)
}
}
func create() {
client := resty.New()
container := CodeFirstContainer{
Image: imageName,
Env: env.value,
Private: private,
Admins: admins,
}
resp, err := client.R().
SetHeader("x-forwarded-user", authUser).
SetBody(container).
Post(fmt.Sprintf("%s://%s/containers/create/%s", proxyScheme, proxyHost, containerName))
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Println(string(resp.Body()))
if resp.IsError() {
os.Exit(1)
}
}
func delete(bypassError bool) {
client := resty.New()
resp, err := client.R().
SetHeader("x-forwarded-user", authUser).
Delete(fmt.Sprintf("%s://%s/containers/%s", proxyScheme, proxyHost, containerName))
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Println(string(resp.Body()))
if !bypassError && resp.IsError() {
os.Exit(1)
}
}
func createImage() {
client := resty.New()
resp, err := client.R().
SetHeader("x-forwarded-user", authUser).
Post(fmt.Sprintf("%s://%s/images/create?fromImage=%s", proxyScheme, proxyHost, imageName))
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Println(string(resp.Body()))
if resp.IsError() {
os.Exit(1)
}
}

@ -0,0 +1,164 @@
const std = @import("std");
const allocator = std.heap.page_allocator;
var client: std.http.Client = .{ .allocator = allocator };
const Command = enum { list, logs, create, delete, start };
const ForwardedUser = "X-Forwarded-User";
const PluginError = error{
InvalidUser,
InvalidCommand,
NoContainerName,
InvalidImage,
};
const CodefirstAuth = struct {
proxyScheme: []const u8,
proxyHost: []const u8,
user: []const u8,
};
const CodefirstContainer = struct {
Id: []const u8,
Image: []const u8,
Admins: []const u8,
Env: [][]const u8,
Private: bool,
};
fn sendRequest(method: std.http.Method, auth: CodefirstAuth, path: []const u8, body: []const u8) !std.http.Status {
const uri = std.Uri.parse(path) catch unreachable;
var headers = std.http.Headers{ .allocator = allocator };
defer headers.deinit();
try headers.append(ForwardedUser, auth.user);
if (body.len > 0) {
try headers.append("Content-Type", "application/json");
}
var req = try client.request(method, uri, headers, .{});
defer req.deinit();
req.transfer_encoding = std.http.Client.RequestTransfer{ .content_length = body.len };
try req.start();
try req.writeAll(body);
try req.finish();
try req.wait();
const responseBody = req.reader().readAllAlloc(allocator, 8192) catch unreachable;
defer allocator.free(responseBody);
std.log.info("{s}", .{responseBody});
return req.response.status;
}
fn exists(auth: CodefirstAuth, containerName: []const u8) !bool {
const path = try std.fmt.allocPrint(allocator, "{s}://{s}/containers/{s}/json", .{ auth.proxyScheme, auth.proxyHost, containerName });
defer allocator.free(path);
var status = try sendRequest(.GET, auth, path, "");
return status == .ok;
}
fn createImage(auth: CodefirstAuth, imageName: []const u8) !void {
const path = try std.fmt.allocPrint(allocator, "{s}://{s}/images/create?fromImage={s}", .{ auth.proxyScheme, auth.proxyHost, imageName });
defer allocator.free(path);
_ = try sendRequest(.POST, auth, path, "");
}
fn create(auth: CodefirstAuth, container: CodefirstContainer, containerName: []const u8) !void {
const path = try std.fmt.allocPrint(allocator, "{s}://{s}/containers/create/{s}", .{ auth.proxyScheme, auth.proxyHost, containerName });
defer allocator.free(path);
var json = std.ArrayList(u8).init(allocator);
defer json.deinit();
try std.json.stringify(container, .{}, json.writer());
_ = try sendRequest(.POST, auth, path, json.items);
}
fn start(auth: CodefirstAuth, imageName: []const u8, containerName: []const u8, env: [][]const u8, private: bool) !void {
const ContainerStart = struct {
Id: []const u8,
Image: []const u8,
Env: [][]const u8,
Private: bool,
};
const command = ContainerStart{
.Id = "",
.Image = imageName,
.Env = env,
.Private = private,
};
const path = try std.fmt.allocPrint(allocator, "{s}://{s}/containers/{s}/start", .{ auth.proxyScheme, auth.proxyHost, containerName });
defer allocator.free(path);
var json = std.ArrayList(u8).init(allocator);
defer json.deinit();
try std.json.stringify(command, .{}, json.writer());
_ = try sendRequest(.POST, auth, path, json.items);
}
fn delete(auth: CodefirstAuth, containerName: []const u8) !void {
const path = try std.fmt.allocPrint(allocator, "{s}://{s}/containers/{s}", .{ auth.proxyScheme, auth.proxyHost, containerName });
defer allocator.free(path);
_ = try sendRequest(.DELETE, auth, path, "");
}
pub fn run() !void {
const command_name = std.os.getenv("PLUGIN_COMMAND") orelse return PluginError.InvalidCommand;
const command = std.meta.stringToEnum(Command, command_name) orelse return PluginError.InvalidCommand;
const auth = CodefirstAuth{
.proxyScheme = std.os.getenv("PROXYSCHEME") orelse "http",
.proxyHost = std.os.getenv("PROXYHOST") orelse "dockerproxy:8080",
.user = std.os.getenv("DRONE_REPO_OWNER") orelse return PluginError.InvalidUser,
};
const containerName = std.os.getenv("PLUGIN_CONTAINER") orelse return PluginError.NoContainerName;
var envs = std.ArrayList([]const u8).init(allocator);
defer envs.deinit();
for (std.os.environ) |env| {
if (std.mem.startsWith(u8, std.mem.span(env), "CODEFIRST_CLIENTDRONE_ENV_")) {
try envs.append(std.mem.span(env)["CODEFIRST_CLIENTDRONE_ENV_".len..]);
}
}
switch (command) {
.create => {
const imageName = std.os.getenv("PLUGIN_IMAGE") orelse return PluginError.InvalidImage;
const private = std.mem.eql(u8, std.os.getenv("PLUGIN_PRIVATE") orelse "false", "true");
const overwrite = std.mem.eql(u8, std.os.getenv("PLUGIN_OVERWRITE") orelse "false", "true");
const admins = std.os.getenv("PLUGIN_ADMINS") orelse auth.user;
const container = CodefirstContainer{
.Id = "",
.Image = imageName,
.Admins = admins,
.Env = envs.items,
.Private = private,
};
if (overwrite) {
try delete(auth, containerName);
}
if (!try exists(auth, containerName)) {
try createImage(auth, imageName);
try create(auth, container, containerName);
try start(auth, imageName, containerName, envs.items, private);
}
},
.start => {
const imageName = std.os.getenv("PLUGIN_IMAGE") orelse return PluginError.InvalidImage;
const private = std.mem.eql(u8, std.os.getenv("PLUGIN_PRIVATE") orelse "false", "true");
try start(auth, imageName, containerName, envs.items, private);
},
.delete => {
try delete(auth, containerName);
},
else => unreachable,
}
}
pub fn main() !void {
run() catch |err| {
switch (err) {
PluginError.InvalidCommand => std.log.err("Invalid command (possible values: list|logs|create|delete|start)", .{}),
PluginError.InvalidUser => std.log.err("Invalid user", .{}),
PluginError.NoContainerName => std.log.err("No container name", .{}),
PluginError.InvalidImage => std.log.err("Invalid image", .{}),
else => std.log.err("{}", .{err}),
}
std.os.exit(1);
};
}
Loading…
Cancel
Save