Compare commits

...

2 Commits

Author SHA1 Message Date
9c0d125995
add example (draft) 2023-01-21 15:02:33 +01:00
98999336dc
remove existing 2023-01-21 15:02:04 +01:00
35 changed files with 208 additions and 1546 deletions

View File

@ -1,4 +0,0 @@
kind: template
load: bust_gobin_default_template.yaml
name: char
data: {}

1
.gitignore vendored
View File

@ -1 +0,0 @@
dist/

View File

@ -1,26 +0,0 @@
# Char
Char is an organizations best friend, it helps with code sharing when either
using a multi-repo strategy or mono repo. The goal of the project is to
facilitate sharing of presets and plugins to reduce the required complexity
developers have to take on.
This project is best suited with a standard library of plugins (which serves the
function of libraries), as well `kjuulh/bust` which is a platform agnostic
CI/task setup
This is in very early stages, and for now it officially supports scaffolding.
## Example
The `examples` folder shows how to load plugins, though presets are still
pending.
```yaml
# file: .char.yml
registry: git.front.kjuulh.io
plugins:
"kjuulh/char#/plugins/gocli":
vars:
name: "char"
```

View File

@ -1,67 +0,0 @@
package char
import (
"context"
"log"
"git.front.kjuulh.io/kjuulh/char/pkg/charcontext"
"github.com/spf13/cobra"
)
type RequiredArg struct {
Required bool
Value string
}
func NewDoCommand(charctx *charcontext.CharContext) *cobra.Command {
cmd := &cobra.Command{
Use: "do",
}
about, err := charctx.About(context.Background())
if err != nil {
log.Fatal(err)
}
for _, a := range about {
for _, c := range a.Commands {
requiredArgs := make(map[string]*RequiredArg, len(c.Args))
for _, arg := range c.Args {
requiredArgs[arg] = &RequiredArg{
Required: false,
}
}
for _, required := range c.Required {
if _, ok := requiredArgs[required]; ok {
requiredArg := requiredArgs[required]
requiredArg.Required = true
}
}
doCmd := &cobra.Command{
Use: c.Name,
RunE: func(cmd *cobra.Command, args []string) error {
if err := cmd.ParseFlags(args); err != nil {
return err
}
if err := charctx.Do(cmd.Context(), a.ClientName, c.Name, nil); err != nil {
return err
}
return nil
},
}
for argName, argValue := range requiredArgs {
doCmd.PersistentFlags().StringVar(&argValue.Value, argName, "", "")
if argValue.Required {
doCmd.MarkPersistentFlagRequired(argName)
}
}
cmd.AddCommand(doCmd)
}
}
return cmd
}

View File

@ -1,13 +0,0 @@
package char
import (
"github.com/spf13/cobra"
)
func NewLimitedCharCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "char",
}
return cmd
}

View File

@ -1,64 +0,0 @@
package char
import (
"fmt"
"git.front.kjuulh.io/kjuulh/char/pkg/charcontext"
"github.com/spf13/cobra"
)
func NewLsCommand(charctx *charcontext.CharContext) *cobra.Command {
cmd := &cobra.Command{
Use: "ls",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
about, err := charctx.About(ctx)
if err != nil {
return err
}
for _, a := range about {
fmt.Printf("plugin: %s\n", a.Name)
fmt.Printf("\tversion: %s\n", a.Version)
fmt.Printf("\tabout: %s\n", a.About)
if len(a.Vars) > 0 {
fmt.Println("\tVars:")
for _, av := range a.Vars {
fmt.Printf("\t\t%s\n", av)
}
}
if len(a.Commands) > 0 {
fmt.Println("\tCommands:")
for _, ac := range a.Commands {
fmt.Printf("\t\t%s\n", ac.Name)
if len(ac.Args) == 0 {
continue
}
fmt.Println("\t\tArgs")
for _, aca := range ac.Args {
isrequired := false
for _, acr := range ac.Required {
if acr == aca {
isrequired = true
}
}
if isrequired {
fmt.Printf("\t\t\t%s: required\n", aca)
} else {
fmt.Printf("\t\t\t%s\n", aca)
}
}
}
}
fmt.Println()
}
return nil
},
}
return cmd
}

View File

@ -1,19 +0,0 @@
package char
import (
"git.front.kjuulh.io/kjuulh/char/pkg/charcontext"
"github.com/spf13/cobra"
)
func NewCharCmd(charctx *charcontext.CharContext) *cobra.Command {
cmd := &cobra.Command{
Use: "char",
}
cmd.AddCommand(
NewLsCommand(charctx),
NewDoCommand(charctx),
)
return cmd
}

View File

@ -1,4 +0,0 @@
registry: git.front.kjuulh.io
plugins:
"kjuulh/char#plugins/gocli": {}
"kjuulh/char#plugins/rust": {}

View File

@ -1,2 +0,0 @@
.char/plugins/
char

View File

@ -1,21 +0,0 @@
#!/bin/bash
set -e
go build -o char ../../main.go
function devcharls() {
CHAR_DEV_MODE=true ./char ls 2&> /dev/null
}
function charls() {
./char ls 2&> /dev/null
}
echo "scratch"
time devcharls
echo ""
echo "ready"
time charls
echo ""

View File

@ -1,7 +0,0 @@
#!/bin/bash
set -e
go build -o char ../../main.go
CHAR_DEV_MODE=true ./char ls

View File

@ -1,13 +0,0 @@
#!/bin/bash
set -e
go build -o char ../../main.go
echo "base"
CHAR_DEV_MODE=true ./char do -h
echo
echo "--------"
echo "local_up"
CHAR_DEV_MODE=false ./char do local_up --fish something

View File

View File

@ -0,0 +1,208 @@
# Pipelines design docs
The goal of this experiment is to play with various code sharing features. The
pipelines in this experiment is supposed to model normal code sharing behavior
of libraries, with overriding capabilities of the downstream repository
The goal is to split the body of the work in three parts.
- libraries,
- compendiums
- articles
The terminology is as such:
## Libraries
Libraries provide raw functions, and is a general abstraction on an underlying
process, such as running a container, executing a shell script etc. Libraries
might define an API descripting how to interact with it. It is up to the
compendium, and article to use these in a sane manner.
Such that:
```rust
pub fn execute_shell(&self, input: &ShellOpts) -> Result<ShellOutput> {
...
}
```
These work similar to raw primitive functions, but should serve as an
opinionated flyweight. These may be extremely specific, such as building a go
binary, creating a github release etc. The details are left to the caller.
A version scheme of the library should follow semver, as that is the best model
at the moment for versioning. Libraries should be pulled using the native
package manager, or include as a submodule.
## Compendiums
A compendium is an opinionated collection of libraries, which consists of files,
configurations etc. A compendium is to be used by either other compendiums, or
articles. They are not to be used by libraries, this is to provide a natural
hierachy. The end result should be a directed acyclic graph.
A compendium should provide an API for either other compendiums, or articles.
These apis, need to remain flexible, and be open to mutations. As such all
primitive features, files, configurations need to be exposed as raw data or data
structures if suitable.
This is done using pipelines, or middleware for the specific parts. A data
object will pass from above, containing the data to implement the required
interfaces of the Compendium, these must be fulfilled for the construction of
the Compendium, else the compilation should fail.
The caller will have the ability to replace the specifics of the Compendium, by
replacing certain pipelines, or mutating the data. In case of mutations, the
data is only modified for that pipeline and below, if a fork occurs above, then
the divergent paths won't be affected.
Compendiums are driven by pipelines applied to them either from other
compendiums or articles. An article or compendium will only have access to their
direct dependent compendiums pipelines. An article will naturally expose
pipelines to be called by the user.
```rust
pub struct GoApplication;
impl Compendium for GoApplication {
type Input = GoApplicationOpts;
pub fn get_pipelines(&mut self) -> Result<Pipelines> {
let pipelines = self.pipelines
.clone()
.add(self.get_application_pipelines())?
.add(self.get_go_releaser_pipelines())?;
Ok(pipelines)
}
}
```
## Articles
An article is a specific implementation of a compendium, it is the end of the
chain, and is meant to be directly executed by the user, using a client
application.
It by default is supposed to be a golden path, I.e. it passes the defaults of
the Compendium, but on a case-by-case basis has the ability to modify its
pipelines to its needs. This may be changing certain default configurations,
mutate a dockerfile, add additional steps to a pipeline etc, and remove others.
We reason that once you stray from the golden path, you should be in control,
this may be done by forking the compendium's features.
It provides pipelines as actions, and implements a strict protocol for
communication.
```rust
pub struct MyGoService;
impl Article for MyGoService {
fn get_pipelines(&mut self) -> Result<Pipelines> {
let mut go_pipelines = GoApplication::new().get_pipelines()?;
let api_pipeline = self.get_api_pipeline()?;
let build_pipeline = self.get_docker_pipeline(go_pipelines.extract::<BuildPipeline>()?)?;;
let pipelines = PipelineBuilder::new()
.append(go_pipelines)
.append(api_pipeline)
.append(build_pipeline)
.build()?
Ok(pipelines)
}
}
```
## Usage
A host app can now call these:
```bash
char ls
```
ls simply displays the information on what pipeline are available
```bash
char run build
```
run build will execute the pipeline build. It will validate input available from
char.toml, push these keys/values through to the pipeline, which will go through
all the steps.
- build
- MyGoService
- DockerBuildPipeline
- (BuildPipeline)
- DockerLibrary
- `fn docker_build(dockerfile_contents: string)`
- Native dependencies
- "write dockerfile /tmp/abc/dockerfile"
- "shell -> docker build -f /tmp/abc/dockerfile some-library-path"
## Common scenarios
### Replacing parts of a build script (dockerfile)
A compendium may embed/provide a dockerfile or any other resource, these are
provided through consistent interfaces.
```rust
pub struct GoDockerBuildPipeline;
impl GoDockerBuildPipeline {
fn get_resources(&self) -> Result<(
libraries::docker::DockerContents,
libraries::docker::DockerBuildTags)> {
return (self.contents, self.build_tags)
}
fn mutate_resources(
&mut self,
contents: libraries::docker::DockerContents,
build_tags: libraries::docker::DockerBuildTags,
) -> Result<()> {
self.contents = contents;
self.build_tags = build_tags
}
}
impl BuildPipeline for GoDockerBuildPipeline {
fn execute(&mut self, config: Configuration) -> Result<()> {
let (docker_contents, base_tags) = self.get_resources()
libraries::docker::build(docker_contents, base_tags, config)
}
}
```
In the article you can now replace the resources to fit your needs, that or
building your own pipeline.
```rust
pub struct MyGoService;
impl Article for MyGoService {
fn get_pipelines(&mut self) -> Result<Pipelines> {
let go_app = GoApplication::new();
let go_pipelines = go_app.get_pipelines();
let mut go_build_pipeline = go_pipelines.get_pipeline::<GoDockerBuildPipeline>()?;
let go_resources = go_build_pipeline.get_resources()?;
let go_resources = self.mutate_go_resources(&go_resources)?;
go_build_pipeline.mutate_resources(go_resources)?;
go_pipelines.replace::<GoDockerBuildPipeline>(go_build_pipeline)?;
let pipelines = PipelineBuilder::new()
.append(.get_pipelines())
.build()?
Ok(pipelines)
}
}
```

31
go.mod
View File

@ -1,31 +0,0 @@
module git.front.kjuulh.io/kjuulh/char
go 1.19
require (
github.com/hashicorp/go-hclog v1.3.1
github.com/hashicorp/go-plugin v1.4.6
github.com/spf13/cobra v1.6.1
github.com/stretchr/testify v1.8.1
golang.org/x/sync v0.1.0
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/golang/protobuf v1.3.4 // indirect
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 // indirect
github.com/oklog/run v1.0.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/net v0.0.0-20190311183353-d8887717615a // indirect
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect
golang.org/x/text v0.3.0 // indirect
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 // indirect
google.golang.org/grpc v1.27.1 // indirect
)

119
go.sum
View File

@ -1,119 +0,0 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/hashicorp/go-hclog v0.14.1 h1:nQcJDQwIAGnmoUWp8ubocEX40cCml/17YkF6csQLReU=
github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-hclog v1.3.1 h1:vDwF1DFNZhntP4DAjuTpOw3uEgMUpXh1pB5fW9DqHpo=
github.com/hashicorp/go-hclog v1.3.1/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-plugin v1.4.5 h1:oTE/oQR4eghggRg8VY7PAz3dr++VwDNBGCcOfIvHpBo=
github.com/hashicorp/go-plugin v1.4.5/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s=
github.com/hashicorp/go-plugin v1.4.6 h1:MDV3UrKQBM3du3G7MApDGvOsMYy3JQJ4exhSoKBAeVA=
github.com/hashicorp/go-plugin v1.4.6/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 h1:7GoSOOW2jpsfkntVKaS2rAr1TJqfcxotyaUcuxoZSzg=
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191008105621-543471e840be h1:QAcqgptGM8IQBC9K/RC4o+O9YmqEm0diQn9QmZw/0mU=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -1,3 +0,0 @@
go 1.19
use .

View File

@ -1 +0,0 @@
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

29
main.go
View File

@ -1,29 +0,0 @@
package main
import (
"context"
"errors"
"log"
"git.front.kjuulh.io/kjuulh/char/cmd/char"
"git.front.kjuulh.io/kjuulh/char/pkg/charcontext"
)
func main() {
charctx, err := charcontext.NewCharContext(context.Background())
if err != nil {
if errors.Is(err, charcontext.ErrNoContextFound) {
log.Print("you are not in a char context, as such you will be presented with limited options")
if err := char.NewLimitedCharCmd().Execute(); err != nil {
log.Fatal(err)
}
} else {
log.Fatal(err)
}
}
defer charctx.Close()
if err := char.NewCharCmd(charctx).Execute(); err != nil {
log.Fatal(err)
}
}

View File

@ -1,70 +0,0 @@
package charcontext
import (
"context"
"log"
"git.front.kjuulh.io/kjuulh/char/pkg/plugins/provider"
"git.front.kjuulh.io/kjuulh/char/pkg/register"
"git.front.kjuulh.io/kjuulh/char/pkg/schema"
)
type CharContext struct {
contextPath string
pluginRegister *register.PluginRegister
schema *schema.CharSchema
}
func NewCharContext(ctx context.Context) (*CharContext, error) {
localPath, err := FindLocalRoot(ctx)
if err != nil {
return nil, err
}
gpp := provider.NewGitPluginProvider()
s, err := schema.ParseFile(ctx, ".char.yml")
if err != nil {
return nil, err
}
plugins, err := s.GetPlugins(ctx)
if err != nil {
return nil, err
}
err = gpp.FetchPlugins(ctx, s.Registry, plugins)
if err != nil {
return nil, err
}
builder := register.NewPluginRegisterBuilder()
for name, plugin := range plugins {
builder = builder.Add(name.Hash(), plugin.Opts.Path)
}
r, err := builder.Build(ctx)
if err != nil {
return nil, err
}
return &CharContext{
contextPath: localPath,
pluginRegister: r,
schema: s,
}, nil
}
func (cc *CharContext) Close() {
if err := cc.pluginRegister.Close(); err != nil {
log.Fatal(err)
}
}
func (cc *CharContext) About(ctx context.Context) ([]register.AboutItem, error) {
return cc.pluginRegister.About(ctx)
}
func (cc *CharContext) Do(ctx context.Context, clientName string, commandName string, args map[string]string) error {
return cc.pluginRegister.Do(ctx, clientName, commandName, args)
}

View File

@ -1,57 +0,0 @@
package charcontext
import (
"context"
"errors"
"os"
"path"
)
var ErrNoContextFound = errors.New("could not find project root")
const CharFileName = ".char.yml"
func FindLocalRoot(ctx context.Context) (string, error) {
curdir, err := os.Getwd()
if err != nil {
return "", err
}
return recursiveFindLocalRoot(ctx, curdir)
//output, err := exec.Command("git", "rev-parse", "--show-toplevel").CombinedOutput()
//if err != nil {
// return "", err
//}
//if len(output) == 0 {
// return "", errors.New("could not find absolute path")
//}
//if _, err := os.Stat(string(output)); errors.Is(err, os.ErrNotExist) {
// return "", fmt.Errorf("path does not exist %s", string(output))
//}
//return string(output), nil
}
func recursiveFindLocalRoot(ctx context.Context, localpath string) (string, error) {
entries, err := os.ReadDir(localpath)
if err != nil {
return "", err
}
for _, entry := range entries {
if entry.Name() == CharFileName {
return localpath, nil
}
}
if localpath == "/" {
return "", ErrNoContextFound
}
return recursiveFindLocalRoot(ctx, path.Dir(localpath))
}
func ChangeToPath(_ context.Context, path string) error {
return os.Chdir(path)
}

View File

@ -1,107 +0,0 @@
package provider
import (
"context"
"errors"
"fmt"
"log"
"os"
"os/exec"
"strings"
"time"
"git.front.kjuulh.io/kjuulh/char/pkg/schema"
"golang.org/x/sync/errgroup"
)
type GitPluginProvider struct{}
func NewGitPluginProvider() *GitPluginProvider {
return &GitPluginProvider{}
}
func (gpp *GitPluginProvider) FetchPlugins(ctx context.Context, registry string, plugins schema.CharSchemaPlugins) error {
errgroup, ctx := errgroup.WithContext(ctx)
baseDir := ".char/plugins"
if os.Getenv("CHAR_DEV_MODE") == "true" {
if err := os.RemoveAll(baseDir); err != nil {
return err
}
}
if err := os.MkdirAll(baseDir, 0755); err != nil {
return fmt.Errorf("path already exists cannot create: %w", err)
}
for n, plugin := range plugins {
n, plugin := n, plugin
errgroup.Go(func() error {
dest := fmt.Sprintf(
"%s/%s",
strings.TrimRight(baseDir, "/"), n.Hash(),
)
fileinfo, err := os.Stat(dest)
if errors.Is(err, os.ErrNotExist) {
log.Printf("fetching git plugin repo: %s", n)
return gpp.FetchPlugin(
ctx,
registry,
plugin,
dest,
)
}
if fileinfo.ModTime().Add(time.Hour * 1).Before(time.Now()) {
log.Printf("fetching git plugin repo: %s as it is stale", n)
return gpp.FetchPlugin(
ctx,
registry,
plugin,
dest,
)
}
return nil
})
}
if err := errgroup.Wait(); err != nil {
return err
}
return nil
}
func (gpp *GitPluginProvider) FetchPlugin(ctx context.Context, registry string, plugin *schema.CharSchemaPlugin, dest string) error {
cloneUrl, err := plugin.Opts.GetCloneUrl(
ctx,
registry,
&schema.CloneUrlOpt{
Protocol: schema.GitProtocolSsh,
SshUser: "git",
},
)
if err != nil {
return err
}
if _, err := os.Stat(dest); !errors.Is(err, os.ErrNotExist) {
if err = os.RemoveAll(dest); err != nil {
return err
}
}
output, err := exec.Command(
"git",
"clone",
"--depth=1",
cloneUrl,
dest,
).CombinedOutput()
if len(output) > 0 {
log.Print(string(output))
}
if err != nil {
return err
}
return nil
}

View File

@ -1,29 +0,0 @@
package register
import "context"
type AboutCommand struct {
Name string `json:"name" yaml:"name"`
Args []string `json:"args" yaml:"args"`
Required []string `json:"required" yaml:"required"`
}
type About struct {
Name string `json:"name"`
Version string `json:"version"`
About string `json:"about"`
Vars []string `json:"vars"`
Commands []*AboutCommand `json:"commands"`
}
type DoCommand struct {
CommandName string `json:"commandName"`
Args map[string]string `json:"args"`
}
type Plugin interface {
About(ctx context.Context) (*About, error)
Do(ctx context.Context, cmd *DoCommand) error
}
const PluginKey = "plugin"

View File

@ -1,20 +0,0 @@
package register
import (
"net/rpc"
"github.com/hashicorp/go-plugin"
)
type PluginAPI struct {
path string
Impl Plugin
}
func (pa *PluginAPI) Server(*plugin.MuxBroker) (any, error) {
return &PluginServer{Impl: pa.Impl}, nil
}
func (*PluginAPI) Client(b *plugin.MuxBroker, c *rpc.Client) (any, error) {
return &PluginClient{client: c}, nil
}

View File

@ -1,48 +0,0 @@
package register
import (
"context"
"os"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-plugin"
)
type PluginBuilder struct {
serveConfig *plugin.ServeConfig
}
func NewPluginBuilder(p Plugin) *PluginBuilder {
logger := hclog.New(&hclog.LoggerOptions{
Level: hclog.Debug,
Output: os.Stderr,
JSONFormat: false,
})
var pluginMap = map[string]plugin.Plugin{
PluginKey: &PluginAPI{
Impl: p,
},
}
serveConfig := &plugin.ServeConfig{
HandshakeConfig: plugin.HandshakeConfig{
ProtocolVersion: 1,
MagicCookieKey: "BASIC_PLUGIN",
MagicCookieValue: "char",
},
Plugins: pluginMap,
Logger: logger,
}
return &PluginBuilder{
serveConfig: serveConfig,
}
}
func (pr *PluginBuilder) Serve(ctx context.Context) error {
plugin.Serve(
pr.serveConfig,
)
return nil
}

View File

@ -1,39 +0,0 @@
package register
import (
"context"
"encoding/json"
"net/rpc"
)
type PluginClient struct {
client *rpc.Client
}
// Do implements Plugin
func (pc *PluginClient) Do(ctx context.Context, cmd *DoCommand) error {
err := pc.client.Call("Plugin.Do", cmd, new(string))
if err != nil {
return err
}
return nil
}
var _ Plugin = &PluginClient{}
func (pc *PluginClient) About(ctx context.Context) (*About, error) {
var resp string
err := pc.client.Call("Plugin.About", new(any), &resp)
if err != nil {
return nil, err
}
var about About
err = json.Unmarshal([]byte(resp), &about)
if err != nil {
return nil, err
}
return &about, nil
}

View File

@ -1,248 +0,0 @@
package register
import (
"context"
"errors"
"fmt"
"log"
"os"
"os/exec"
"strings"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-plugin"
"golang.org/x/sync/errgroup"
)
type PluginRegisterBuilder struct {
plugins map[string]PluginAPI
}
func NewPluginRegisterBuilder() *PluginRegisterBuilder {
return &PluginRegisterBuilder{
plugins: make(map[string]PluginAPI),
}
}
func (pr *PluginRegisterBuilder) Add(name, path string) *PluginRegisterBuilder {
pr.plugins[name] = PluginAPI{
path: path,
}
return pr
}
func (pr *PluginRegisterBuilder) Build(ctx context.Context) (*PluginRegister, error) {
clients := make(map[string]*pluginClientWrapper, 0)
errgroup, _ := errgroup.WithContext(ctx)
if err := os.MkdirAll(".char/plugins/", 0755); err != nil {
return nil, err
}
for name, p := range pr.plugins {
name, p := name, p
errgroup.Go(func() error {
pluginPath := fmt.Sprintf(".char/plugins/%s/dist/plugin", name)
_, err := os.Stat(pluginPath)
if err != nil || os.Getenv("CHAR_DEV_MODE") == "true" {
log.Printf("building: %s", name)
cmd := exec.Command(
"sh",
"-c",
fmt.Sprintf(
"(cd .char/plugins/%s; go build -o dist/plugin %s/main.go)",
name,
strings.TrimSuffix(
strings.TrimSuffix(
p.path,
"main.go",
),
"/",
),
),
)
output, err := cmd.CombinedOutput()
if len(output) > 0 {
log.Println(string(output))
}
if err != nil {
return fmt.Errorf("could not build plugin: %w", err)
}
}
client := plugin.NewClient(&plugin.ClientConfig{
HandshakeConfig: plugin.HandshakeConfig{
ProtocolVersion: 1,
MagicCookieKey: "BASIC_PLUGIN",
MagicCookieValue: "char",
},
Logger: hclog.New(&hclog.LoggerOptions{
Name: "char",
Output: os.Stdout,
Level: hclog.Debug,
}),
Cmd: exec.Command(
fmt.Sprintf(
".char/plugins/%s/dist/plugin",
name,
),
),
Plugins: map[string]plugin.Plugin{
PluginKey: &p,
},
})
rpcClient, err := client.Client()
if err != nil {
return err
}
raw, err := rpcClient.Dispense("plugin")
if err != nil {
return err
}
pluginApi, ok := raw.(Plugin)
if !ok {
return errors.New("could not cast as plugin")
}
clients[name] = &pluginClientWrapper{
plugin: pluginApi,
client: client,
}
return nil
})
}
err := errgroup.Wait()
if err != nil {
return nil, err
}
return &PluginRegister{
clients: clients,
}, nil
}
// ---
type pluginClientWrapper struct {
plugin Plugin
client *plugin.Client
}
func (pcw *pluginClientWrapper) Close() {
pcw.client.Kill()
}
// ---
type PluginRegister struct {
clients map[string]*pluginClientWrapper
}
func (pr *PluginRegister) Close() error {
errgroup, _ := errgroup.WithContext(context.Background())
for _, c := range pr.clients {
c := c
errgroup.Go(func() error {
c.Close()
return nil
})
}
if err := errgroup.Wait(); err != nil {
return err
}
return nil
}
type CommandAboutItem struct {
Name string
Args []string
Required []string
}
type CommandAboutItems []*CommandAboutItem
func FromAboutCommands(commands []*AboutCommand) CommandAboutItems {
cai := make(CommandAboutItems, 0)
for _, command := range commands {
cai = append(cai, &CommandAboutItem{
Name: command.Name,
Args: command.Args,
Required: command.Required,
})
}
return cai
}
type AboutItem struct {
Name string
Version string
About string
Vars []string
Commands CommandAboutItems
ClientName string
}
func (pr *PluginRegister) About(ctx context.Context) ([]AboutItem, error) {
list := make([]AboutItem, 0)
errgroup, ctx := errgroup.WithContext(ctx)
for name, c := range pr.clients {
name, c := name, c
errgroup.Go(func() error {
about, err := c.plugin.About(ctx)
if err != nil {
return err
}
list = append(list, AboutItem{
Name: about.Name,
Version: about.Version,
About: about.About,
Vars: about.Vars,
Commands: FromAboutCommands(about.Commands),
ClientName: name,
})
return nil
})
}
if err := errgroup.Wait(); err != nil {
return nil, err
}
return list, nil
}
func (pr *PluginRegister) Do(ctx context.Context, clientName string, commandName string, args map[string]string) error {
errgroup, ctx := errgroup.WithContext(ctx)
client, ok := pr.clients[clientName]
if !ok {
return fmt.Errorf("plugin was not found: %s", clientName)
}
errgroup.Go(func() error {
return client.plugin.Do(ctx, &DoCommand{
CommandName: commandName,
Args: args,
})
})
if err := errgroup.Wait(); err != nil {
return err
}
return nil
}

View File

@ -1,44 +0,0 @@
package register
import (
"context"
"encoding/json"
)
type PluginServer struct {
Impl Plugin
}
func (ps *PluginServer) Do(args *DoCommand, resp *string) error {
//rawReq, ok := args.(string)
//if !ok {
// return errors.New("args is not a string")
//}
//var doReq DoRequest
//if err := json.Unmarshal([]byte(rawReq), &doReq); err != nil {
// return err
//}
if err := ps.Impl.Do(context.Background(), args); err != nil {
return err
}
*resp = ""
return nil
}
func (ps *PluginServer) About(args any, resp *string) error {
r, err := ps.Impl.About(context.Background())
if err != nil {
return err
}
respB, err := json.Marshal(r)
if err != nil {
return err
}
*resp = string(respB)
return nil
}

View File

@ -1,50 +0,0 @@
package schema
import (
"context"
"errors"
"fmt"
"os"
"gopkg.in/yaml.v3"
)
type CharSchema struct {
Registry string `json:"registry" yaml:"registry"`
Plugins CharSchemaPlugins `json:"plugins" yaml:"plugins"`
}
func ParseFile(ctx context.Context, path string) (*CharSchema, error) {
if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
return nil, fmt.Errorf("could not parse file, as it is not found or permitted: %s", path)
}
file, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("could not read file: %w", err)
}
return Parse(file)
}
func Parse(content []byte) (*CharSchema, error) {
var schema CharSchema
if err := yaml.Unmarshal(content, &schema); err != nil {
return nil, fmt.Errorf("could not deserialize yaml into CharSchema: %w", err)
}
return &schema, nil
}
func (cs *CharSchema) GetPlugins(ctx context.Context) (CharSchemaPlugins, error) {
plugins := make(map[CharSchemaPluginName]*CharSchemaPlugin, len(cs.Plugins))
for n, plugin := range cs.Plugins {
po, err := n.Get()
if err != nil {
return nil, err
}
plugin.Opts = po
plugins[n] = plugin
}
return plugins, nil
}

View File

@ -1,104 +0,0 @@
package schema
import (
"context"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"regexp"
"strings"
)
type CharSchemaPluginName string
func (cspn CharSchemaPluginName) Hash() string {
bytes := sha256.Sum256([]byte(cspn))
return hex.EncodeToString(bytes[:])
}
type PluginOps struct {
Org string
RepositoryName string
Path string
Version string
}
type GitProtocol string
const (
GitProtocolHttps GitProtocol = "https"
GitProtocolSsh = "ssh"
)
type CloneUrlOpt struct {
Protocol GitProtocol
SshUser string
}
func (po *PluginOps) GetCloneUrl(ctx context.Context, registry string, opt *CloneUrlOpt) (string, error) {
if opt == nil {
return "", errors.New("opt is required")
}
switch opt.Protocol {
case GitProtocolHttps:
return fmt.Sprintf("https://%s/%s/%s.git", registry, po.Org, po.RepositoryName), nil
case GitProtocolSsh:
return fmt.Sprintf("%s@%s:%s/%s.git", opt.SshUser, registry, po.Org, po.RepositoryName), nil
default:
return "", errors.New("protocol not allowed")
}
}
var memo = map[string]*PluginOps{}
func (cspn CharSchemaPluginName) Get() (*PluginOps, error) {
if m, ok := memo[string(cspn)]; ok {
return m, nil
}
po := &PluginOps{}
reg := regexp.MustCompile(
`(?P<org>[\d\w\-_\.]+)\/(?P<repo>[\d\w\-_\.]+)(?P<path>#[\d\w\-_\.\/]+)?(?P<version>@[\d\w\-_\.\/]+)?(?P<path>#[\d\w\-_\.\/]+)?`,
)
matches := reg.FindStringSubmatch(string(cspn))
tags := reg.SubexpNames()
matchTags := make(map[string]string, len(matches))
for i, match := range matches {
tag := tags[i]
if existingTag, ok := matchTags[tag]; !ok || existingTag == "" {
matchTags[tag] = match
}
}
if org, ok := matchTags["org"]; ok {
po.Org = org
}
if repo, ok := matchTags["repo"]; ok {
po.RepositoryName = repo
}
if path, ok := matchTags["path"]; ok {
po.Path = strings.TrimLeft(path, "#")
}
if version, ok := matchTags["version"]; ok {
po.Version = strings.TrimLeft(version, "@")
}
if po.Org == "" || po.RepositoryName == "" {
return nil, errors.New("could not find org or repository name")
}
memo[string(cspn)] = po
return po, nil
}
type CharSchemaPlugins map[CharSchemaPluginName]*CharSchemaPlugin
type CharSchemaPluginVarName string
type CharSchemaPluginVars map[CharSchemaPluginVarName]string
type CharSchemaPlugin struct {
Opts *PluginOps
Vars CharSchemaPluginVars `json:"vars"`
}

View File

@ -1,126 +0,0 @@
package schema_test
import (
"context"
"testing"
"git.front.kjuulh.io/kjuulh/char/pkg/schema"
"github.com/stretchr/testify/require"
)
func TestSchemaNameCanParse(t *testing.T) {
t.Parallel()
tt := []struct {
name string
inputString schema.CharSchemaPluginName
expected schema.PluginOps
}{
{
name: "default string",
inputString: `kju123K_-ulh/someRepo-._123`,
expected: schema.PluginOps{
Org: "kju123K_-ulh",
RepositoryName: "someRepo-._123",
},
},
{
name: "default string with path",
inputString: `kju123K_-ulh/someRepo-._123#somepath/sometoherpath/somethridpath`,
expected: schema.PluginOps{
Org: "kju123K_-ulh",
RepositoryName: "someRepo-._123",
Path: "somepath/sometoherpath/somethridpath",
},
},
{
name: "default string with version",
inputString: `kju123K_-ulh/someRepo-._123@12l3.jk1lj`,
expected: schema.PluginOps{
Org: "kju123K_-ulh",
RepositoryName: "someRepo-._123",
Version: "12l3.jk1lj",
},
},
{
name: "default string with version and path",
inputString: `kju123K_-ulh/someRepo-._123@12l3.jk1lj#somepath/sometoherpath/somethridpath`,
expected: schema.PluginOps{
Org: "kju123K_-ulh",
RepositoryName: "someRepo-._123",
Version: "12l3.jk1lj",
Path: "somepath/sometoherpath/somethridpath",
},
},
{
name: "default string with path and version",
inputString: `kju123K_-ulh/someRepo-._123#somepath/sometoherpath/somethridpath@12l3.jk1lj`,
expected: schema.PluginOps{
Org: "kju123K_-ulh",
RepositoryName: "someRepo-._123",
Version: "12l3.jk1lj",
Path: "somepath/sometoherpath/somethridpath",
},
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
actual, _ := tc.inputString.Get()
require.Equal(t, tc.expected, *actual)
})
}
}
func TestPluginOpt(t *testing.T) {
t.Parallel()
tt := []struct {
name string
pluginOpt schema.PluginOps
cloneUrlOpt schema.CloneUrlOpt
registry string
expected string
}{
{
name: "ssh values",
pluginOpt: schema.PluginOps{
Org: "kjuulh",
RepositoryName: "char",
Path: "",
Version: "",
},
cloneUrlOpt: schema.CloneUrlOpt{
Protocol: schema.GitProtocolSsh,
SshUser: "git",
},
registry: "git.front.kjuulh.io",
expected: "git@git.front.kjuulh.io:kjuulh/char.git",
},
{
name: "https values",
pluginOpt: schema.PluginOps{
Org: "kjuulh",
RepositoryName: "char",
Path: "",
Version: "",
},
cloneUrlOpt: schema.CloneUrlOpt{
Protocol: schema.GitProtocolHttps,
},
registry: "git.front.kjuulh.io",
expected: "https://git.front.kjuulh.io/kjuulh/char.git",
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
url, err := tc.pluginOpt.GetCloneUrl(context.Background(), tc.registry, &tc.cloneUrlOpt)
require.NoError(t, err)
require.Equal(t, tc.expected, url)
})
}
}

View File

@ -1,93 +0,0 @@
package schema_test
import (
"context"
"testing"
"git.front.kjuulh.io/kjuulh/char/pkg/schema"
"github.com/stretchr/testify/require"
)
func TestSchemaParse(t *testing.T) {
t.Parallel()
tt := []struct {
name string
input string
expected *schema.CharSchema
}{
{
name: "with plugins",
input: `
registry: git.front.kjuulh.io
plugins:
"kjuulh/char#plugins/gocli": {}
"kjuulh/char#plugins/rust": {}
`,
expected: &schema.CharSchema{
Registry: "git.front.kjuulh.io",
Plugins: map[schema.CharSchemaPluginName]*schema.CharSchemaPlugin{
"kjuulh/char#plugins/gocli": {},
"kjuulh/char#plugins/rust": {},
},
},
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
s, err := schema.Parse([]byte(tc.input))
require.NoError(t, err)
require.Equal(t, tc.expected, s)
})
}
}
func TestGetPlugins(t *testing.T) {
t.Parallel()
tt := []struct {
name string
input string
expected schema.CharSchemaPlugins
}{
{
name: "with plugins",
input: `
registry: git.front.kjuulh.io
plugins:
"kjuulh/char#plugins/gocli@v1.9.0": {}
"kjuulh/char#plugins/rust": {}
`,
expected: map[schema.CharSchemaPluginName]*schema.CharSchemaPlugin{
"kjuulh/char#plugins/gocli@v1.9.0": {
Opts: &schema.PluginOps{
Org: "kjuulh",
RepositoryName: "char",
Path: "plugins/gocli",
Version: "v1.9.0",
},
},
"kjuulh/char#plugins/rust": {
Opts: &schema.PluginOps{
Org: "kjuulh",
RepositoryName: "char",
Path: "plugins/rust",
Version: "",
},
},
},
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
s, err := schema.Parse([]byte(tc.input))
require.NoError(t, err)
plugins, err := s.GetPlugins(context.Background())
require.NoError(t, err)
require.Equal(t, tc.expected, plugins)
})
}
}

View File

@ -1,47 +0,0 @@
package main
import (
"context"
"log"
"git.front.kjuulh.io/kjuulh/char/pkg/register"
"github.com/hashicorp/go-hclog"
)
type GoCliPlugin struct{}
// Do implements register.Plugin
func (*GoCliPlugin) Do(ctx context.Context, cmd *register.DoCommand) error {
hclog.L().Info("received command", "commandName", cmd.CommandName)
return nil
}
func (*GoCliPlugin) About(ctx context.Context) (*register.About, error) {
return &register.About{
Name: "gocli",
Version: "v0.0.1",
About: "golang cli provides a set of actions and presets supporting golang development",
Vars: []string{
"dev.mode",
},
Commands: []*register.AboutCommand{
{
Name: "local_up",
Args: []string{"fish"},
Required: []string{"fish"},
},
},
}, nil
}
var _ register.Plugin = &GoCliPlugin{}
func main() {
if err := register.
NewPluginBuilder(
&GoCliPlugin{},
).
Serve(context.Background()); err != nil {
log.Fatal(err)
}
}

View File

@ -1,37 +0,0 @@
package main
import (
"context"
"log"
"git.front.kjuulh.io/kjuulh/char/pkg/register"
"github.com/hashicorp/go-hclog"
)
type GoCliPlugin struct{}
// Do implements register.Plugin
func (*GoCliPlugin) Do(ctx context.Context, cmd *register.DoCommand) error {
hclog.L().Info("received command", "commandName", cmd.CommandName)
return nil
}
func (*GoCliPlugin) About(ctx context.Context) (*register.About, error) {
return &register.About{
Name: "rust",
Version: "v0.0.1",
About: "rust cli provides a set of actions and presets supporting rust development",
}, nil
}
var _ register.Plugin = &GoCliPlugin{}
func main() {
if err := register.
NewPluginBuilder(
&GoCliPlugin{},
).
Serve(context.Background()); err != nil {
log.Fatal(err)
}
}

View File

@ -1,3 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
}