Skip to content

Commit

Permalink
Add pr template command (#485)
Browse files Browse the repository at this point in the history
* Add pr template command

Will be used for on-demand pr gen

* switch to liquid templating to remain consistent w/ api
  • Loading branch information
michaeljguarino authored Jan 23, 2024
1 parent 3861ee3 commit 8d53ef3
Show file tree
Hide file tree
Showing 8 changed files with 294 additions and 49 deletions.
102 changes: 53 additions & 49 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: us-east-2
role-to-assume: arn:aws:iam::312272277431:role/github-actions/buildx-deployments
role-session-name: PluralCLI
- name: setup kubectl
uses: azure/setup-kubectl@v3
- name: Get EKS credentials
run: aws eks update-kubeconfig --name pluraldev
# - name: Configure AWS Credentials
# uses: aws-actions/configure-aws-credentials@v4
# with:
# aws-region: us-east-2
# role-to-assume: arn:aws:iam::312272277431:role/github-actions/buildx-deployments
# role-session-name: PluralCLI
# - name: setup kubectl
# uses: azure/setup-kubectl@v3
# - name: Get EKS credentials
# run: aws eks update-kubeconfig --name pluraldev
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
Expand All @@ -41,34 +41,38 @@ jobs:
type=sha
type=ref,event=pr
type=ref,event=branch
# - name: Set up Docker Buildx
# id: builder
# uses: docker/setup-buildx-action@v3
# with:
# # cleanup: true
# # driver: kubernetes
# platforms: linux/amd64
# driver-opts: |
# namespace=buildx
# requests.cpu=1.5
# requests.memory=3.5Gi
# "nodeselector=plural.sh/scalingGroup=buildx-spot-x86"
# "tolerations=key=plural.sh/capacityType,value=SPOT,effect=NoSchedule;key=plural.sh/reserved,value=BUILDX,effect=NoSchedule"
# - name: Append ARM buildx builder from AWS
# run: |
# docker buildx create \
# --append \
# --bootstrap \
# --name ${{ steps.builder.outputs.name }} \
# --driver=kubernetes \
# --platform linux/arm64 \
# --node=${{ steps.builder.outputs.name }}-arm64 \
# --buildkitd-flags "--allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host" \
# --driver-opt namespace=buildx \
# --driver-opt requests.cpu=1.5 \
# --driver-opt requests.memory=3.5Gi \
# '--driver-opt="nodeselector=plural.sh/scalingGroup=buildx-spot-arm64"' \
# '--driver-opt="tolerations=key=plural.sh/capacityType,value=SPOT,effect=NoSchedule;key=plural.sh/reserved,value=BUILDX,effect=NoSchedule"'
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
id: builder
uses: docker/setup-buildx-action@v3
with:
cleanup: true
driver: kubernetes
platforms: linux/amd64
driver-opts: |
namespace=buildx
requests.cpu=1.5
requests.memory=3.5Gi
"nodeselector=plural.sh/scalingGroup=buildx-spot-x86"
"tolerations=key=plural.sh/capacityType,value=SPOT,effect=NoSchedule;key=plural.sh/reserved,value=BUILDX,effect=NoSchedule"
- name: Append ARM buildx builder from AWS
run: |
docker buildx create \
--append \
--bootstrap \
--name ${{ steps.builder.outputs.name }} \
--driver=kubernetes \
--platform linux/arm64 \
--node=${{ steps.builder.outputs.name }}-arm64 \
--buildkitd-flags "--allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host" \
--driver-opt namespace=buildx \
--driver-opt requests.cpu=1.5 \
--driver-opt requests.memory=3.5Gi \
'--driver-opt="nodeselector=plural.sh/scalingGroup=buildx-spot-arm64"' \
'--driver-opt="tolerations=key=plural.sh/capacityType,value=SPOT,effect=NoSchedule;key=plural.sh/reserved,value=BUILDX,effect=NoSchedule"'
- name: Login to GHCR
uses: docker/login-action@v2
with:
Expand Down Expand Up @@ -107,19 +111,19 @@ jobs:
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
if: always()
with:
aws-region: us-east-2
role-to-assume: arn:aws:iam::312272277431:role/github-actions/buildx-deployments
role-session-name: PluralCLI
- name: Manually cleanup buildx
if: always()
run: |
docker buildx stop ${{ steps.builder.outputs.name }}
sleep 10
docker buildx rm ${{ steps.builder.outputs.name }}
# - name: Configure AWS Credentials
# uses: aws-actions/configure-aws-credentials@v4
# if: always()
# with:
# aws-region: us-east-2
# role-to-assume: arn:aws:iam::312272277431:role/github-actions/buildx-deployments
# role-session-name: PluralCLI
# - name: Manually cleanup buildx
# if: always()
# run: |
# docker buildx stop ${{ steps.builder.outputs.name }}
# sleep 10
# docker buildx rm ${{ steps.builder.outputs.name }}
cloud:
name: Build cloud image
runs-on: ubuntu-latest
Expand Down
7 changes: 7 additions & 0 deletions cmd/plural/plural.go
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,13 @@ func (p *Plural) getCommands() []cli.Command {
},
Category: "CD",
},
{
Name: "pull-requests",
Aliases: []string{"pr"},
Usage: "Generate and manage pull requests",
Subcommands: prCommands(),
Category: "CD",
},
{
Name: "template",
Aliases: []string{"tpl"},
Expand Down
32 changes: 32 additions & 0 deletions cmd/plural/pr.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package plural

import (
"github.com/pluralsh/plural-cli/pkg/pr"
"github.com/urfave/cli"
)

func prCommands() []cli.Command {
return []cli.Command{
{
Name: "template",
Usage: "applies a pr template resource in the local source tree",
Action: handlePrTemplate,
Flags: []cli.Flag{
cli.StringFlag{
Name: "file",
Usage: "the file the template was placed in",
Required: true,
},
},
},
}
}

func handlePrTemplate(c *cli.Context) error {
template, err := pr.Build(c.String("file"))
if err != nil {
return err
}

return pr.Apply(template)
}
9 changes: 9 additions & 0 deletions pkg/pr/apply.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package pr

func Apply(template *PrTemplate) error {
if err := applyUpdates(template.Spec.Updates, template.Context); err != nil {
return err
}

return applyCreates(template.Spec.Creates, template.Context)
}
5 changes: 5 additions & 0 deletions pkg/pr/creates.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package pr

func applyCreates(creates *CreateSpec, ctx map[string]interface{}) error {
return nil
}
45 changes: 45 additions & 0 deletions pkg/pr/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package pr

import (
"os"

"sigs.k8s.io/yaml"
)

type PrTemplate struct {
ApiVersion string `json:"apiVersion"`
Kind string `json:"kind"`
Metadata map[string]interface{} `json:"metadata"`
Context map[string]interface{} `json:"context"`
Spec PrTemplateSpec `json:"spec"`
}

type PrTemplateSpec struct {
Updates *UpdateSpec `json:"updates"`
Creates *CreateSpec `json:"creates"`
}

type UpdateSpec struct {
Regexes []string `json:"regexes"`
Files []string `json:"files"`
ReplaceTemplate string `json:"replace_template"`
Yq string `json:"yq"`
MatchStrategy string `json:"match_strategy"`
}

type CreateSpec struct {
}

func Build(path string) (*PrTemplate, error) {
pr := &PrTemplate{}
data, err := os.ReadFile(path)
if err != nil {
return pr, err
}

if err := yaml.Unmarshal(data, pr); err != nil {
return pr, err
}

return pr, nil
}
107 changes: 107 additions & 0 deletions pkg/pr/updates.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package pr

import (
"io/fs"
"path/filepath"
"regexp"
)

func applyUpdates(updates *UpdateSpec, ctx map[string]interface{}) error {
replacement, err := templateReplacement([]byte(updates.ReplaceTemplate), ctx)
if err != nil {
return err
}

return filepath.Walk(".", func(path string, info fs.FileInfo, err error) error {
if err != nil {
return err
}

if info.IsDir() {
return nil
}

ok, err := filenameMatches(path, updates.Files)
if err != nil {
return err
}

if ok {
return updateFile(path, updates, replacement)
}

return nil
})
}

func updateFile(path string, updates *UpdateSpec, replacement []byte) error {
switch updates.MatchStrategy {
case "any":
return anyUpdateFile(path, updates, replacement)
case "all":
return allUpdateFile(path, updates)
case "recursive":
return recursiveUpdateFile(path, updates, replacement)
default:
return nil
}
}

func anyUpdateFile(path string, updates *UpdateSpec, replacement []byte) error {
return replaceInPlace(path, func(data []byte) ([]byte, error) {
for _, reg := range updates.Regexes {
r, err := regexp.Compile(reg)
if err != nil {
return data, err
}
data = r.ReplaceAll(data, replacement)
}
return data, nil
})
}

func allUpdateFile(path string, updates *UpdateSpec) error {
return nil
}

func recursiveUpdateFile(path string, updates *UpdateSpec, replacement []byte) error {
return replaceInPlace(path, func(data []byte) ([]byte, error) {
return recursiveReplace(data, updates.Regexes, replacement)
})
}

func recursiveReplace(data []byte, regexes []string, replacement []byte) ([]byte, error) {
if len(regexes) == 0 {
return []byte(replacement), nil
}

r, err := regexp.Compile(regexes[0])
if err != nil {
return data, err
}

res := r.ReplaceAllFunc(data, func(d []byte) []byte {
res, err := recursiveReplace(d, regexes[1:], replacement)
if err != nil {
panic(err)
}
return res
})

return res, nil
}

func filenameMatches(path string, files []string) (bool, error) {
for _, f := range files {
r, err := regexp.Compile(f)
if err != nil {
return false, err
}

if r.MatchString(path) {
return true, nil
}
}

return false, nil
}
36 changes: 36 additions & 0 deletions pkg/pr/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package pr

import (
"os"

"github.com/osteele/liquid"
)

var (
liquidEngine = liquid.NewEngine()
)

func templateReplacement(data []byte, ctx map[string]interface{}) ([]byte, error) {
bindings := map[string]interface{}{
"context": ctx,
}
return liquidEngine.ParseAndRender(data, bindings)
}

func replaceInPlace(path string, rep func(data []byte) ([]byte, error)) error {
info, err := os.Stat(path)
if err != nil {
return err
}

data, err := os.ReadFile(path)
if err != nil {
return err
}

resData, err := rep(data)
if err != nil {
return err
}
return os.WriteFile(path, resData, info.Mode())
}

0 comments on commit 8d53ef3

Please sign in to comment.