Skip to content

Provides web northbound API #218

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Sep 28, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build/Dockerfile.goreleaser.kuesta
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ WORKDIR /bin
COPY kuesta .
COPY --from=golang:1.18 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt

EXPOSE 9339
EXPOSE 9339 8080
ENTRYPOINT ["/bin/kuesta"]
4 changes: 2 additions & 2 deletions build/Dockerfile.kuesta
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ COPY go.sum ./
RUN go mod download

COPY main.go ./
COPY cmd/ cmd/
COPY internal/ internal/
COPY pkg/ pkg/

ARG CGO_ENABLED=0
Expand All @@ -24,6 +24,6 @@ WORKDIR /bin
COPY --from=builder /workspace/kuesta .
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt

EXPOSE 9339
EXPOSE 9339 8080

ENTRYPOINT ["/bin/kuesta"]
3 changes: 3 additions & 0 deletions config/bases/kuesta/kuesta-server.deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ spec:
- name: grpc
containerPort: 9339
protocol: TCP
- name: http
containerPort: 8080
protocol: TCP
volumes:
- name: kuesta-server-data
persistentVolumeClaim:
Expand Down
4 changes: 4 additions & 0 deletions config/bases/kuesta/kuesta-server.svc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ spec:
port: 9339
targetPort: 9339
protocol: TCP
- name: http
port: 8080
targetPort: 8080
protocol: TCP
7 changes: 4 additions & 3 deletions device-operator/internal/model/generated.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ in this case).

This package was generated by /Users/hirokiokui/work/go/pkg/mod/github.com/openconfig/[email protected]/genutil/names.go
using the following YANG input files:
- yang/openconfig-interfaces.yang
- yang/openconfig-if-ip.yang
- yang/openconfig-interfaces.yang
- yang/openconfig-if-ip.yang

Imported modules were sourced from:
- yang/...
- yang/...
*/
package model

Expand Down
6 changes: 6 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/go-playground/validator/v10 v10.11.1
github.com/golang/mock v1.6.0
github.com/golang/protobuf v1.5.2
github.com/labstack/echo v3.3.10+incompatible
github.com/openconfig/gnmi v0.0.0-20220617175856-41246b1b3507
github.com/pkg/errors v0.9.1
github.com/rogpeppe/go-internal v1.8.1
Expand Down Expand Up @@ -57,8 +58,11 @@ require (
github.com/json-iterator/go v1.1.12 // indirect
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/labstack/gommon v0.4.0 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
Expand All @@ -78,6 +82,8 @@ require (
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.3.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.1 // indirect
github.com/xanzy/ssh-agent v0.3.0 // indirect
go.uber.org/atomic v1.7.0 // indirect
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
Expand Down
16 changes: 16 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -234,13 +234,22 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg=
github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
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.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
Expand Down Expand Up @@ -324,6 +333,10 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI=
github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI=
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
Expand Down Expand Up @@ -494,8 +507,11 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/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-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
Expand Down
13 changes: 11 additions & 2 deletions internal/cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (

const (
FlagServeAddr = "serve-addr"
FlagServeHttpAddr = "serve-http-addr"
FlagSyncInterval = "sync-interval"
FlagPersistGitState = "persist-git-state"
)
Expand All @@ -45,11 +46,18 @@ func newServeCmd() *cobra.Command {
return err
}
logger.Setup(cfg.Devel, cfg.Verbose)

return core.RunServe(cmd.Context(), cfg)
serveError := make(chan error)
go func() {
serveError <- core.RunServeHttp(cmd.Context(), cfg)
}()
go func() {
serveError <- core.RunServe(cmd.Context(), cfg)
}()
return <-serveError
},
}
cmd.Flags().StringP(FlagServeAddr, "a", ":9339", "Bind address of gNMI northbound API.")
cmd.Flags().StringP(FlagServeHttpAddr, "", ":8080", "Bind address of http northbound API.")
cmd.Flags().IntP(FlagSyncInterval, "", 10, "Interval to exec git-pull from status repo.")
cmd.Flags().BoolP(FlagPersistGitState, "", false, "Persist git workspace even when api call closed without performing hard-reset.")
mustBindToViper(cmd)
Expand All @@ -65,6 +73,7 @@ func newServeCfg(cmd *cobra.Command, args []string) (*core.ServeCfg, error) {
cfg := &core.ServeCfg{
RootCfg: *rootCfg,
Addr: viper.GetString(FlagServeAddr),
HttpAddr: viper.GetString(FlagServeHttpAddr),
SyncPeriod: viper.GetInt(FlagSyncInterval),
PersistGitState: viper.GetBool(FlagPersistGitState),
NoTLS: viper.GetBool(FlagNoTLS),
Expand Down
3 changes: 2 additions & 1 deletion internal/core/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ type ServeCfg struct {
RootCfg

Addr string `validate:"required"`
SyncPeriod int `validate:"required"`
HttpAddr string
SyncPeriod int `validate:"required"`
PersistGitState bool
NoTLS bool
Insecure bool
Expand Down
211 changes: 211 additions & 0 deletions internal/core/serve_http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
/*
Copyright (c) 2022-2023 NTT Communications Corporation

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

package core

import (
"context"
"encoding/json"
"fmt"
"net"
"net/http"
"os"
"path/filepath"

"cuelang.org/go/cue"
"cuelang.org/go/cue/cuecontext"
"github.com/labstack/echo"
"github.com/nttcom/kuesta/internal/file"
"github.com/nttcom/kuesta/internal/gogit"
"github.com/nttcom/kuesta/internal/logger"
kcue "github.com/nttcom/kuesta/pkg/cue"
"github.com/nttcom/kuesta/pkg/kuesta"
)

type HttpGetBody struct {
Paths []string `json:"paths"`
}

type HttpSetBody struct {
Path string `json:"path"`
Value map[string]interface{} `json:"value"`
}

type HttpSetRes struct {
Before map[string]interface{} `json:"before"`
After map[string]interface{} `json:"after"`
}

func RunServeHttp(ctx context.Context, cfg *ServeCfg) error {
l := logger.FromContext(ctx)
e := echo.New()

l.Infow("starting to listen", "address", 8080)
listen, err := net.Listen("tcp", cfg.HttpAddr)
if err != nil {
e.Logger.Fatal(err)
}
cGit, err := gogit.NewGit(cfg.ConfigGitOptions().ShouldCloneIfNotExist())
if err != nil {
e.Logger.Fatal(err)
}

e.GET("/capabilities", func(c echo.Context) error {
return HttpCapabilities(c, cfg.ConfigRootPath)
})
e.POST("/get", func(c echo.Context) error {
return HttpGet(c, cfg.ConfigRootPath)
})
e.POST("/set", func(c echo.Context) error {
return HttpSet(c, ctx, cGit, cfg)
})

e.Listener = listen
e.Logger.Fatal(e.Start(""))
return nil
}

// Capabilities responds the server capabilities containing the available services.
func HttpCapabilities(c echo.Context, path string) error {
c.Logger().Info("CapabilityRequest called for http")
mlist, err := kuesta.ReadServiceMetaAll(path)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("HttpCapabilities error: ReadServiceMetaAll: %v", err))
}
return c.JSON(http.StatusOK, mlist)
}

// Get responds the multiple service inputs requested by GetRequest.
func HttpGet(c echo.Context, rootpath string) error {
c.Logger().Info("get called for http")
req := HttpGetBody{}
if err := c.Bind(&req); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("HttpGet Please provide valid credentials: %v", err))
}

res := make([]interface{}, 0)
for _, v := range req.Paths {
path := filepath.Clean(filepath.Join(rootpath, v, "input.cue"))
buf, err := os.ReadFile(path)

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression

This path depends on a [user-provided value](1).
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("HttpGet error: ReadFile: %v", err))
}
cctx := cuecontext.New()
val, err := kcue.NewValueFromBytes(cctx, buf)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("HttpGet error: NewValueFromBytes: %v", err))
}
jsonDump, err := val.MarshalJSON()
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("HttpGet error: MarshalJSON: %v", err))
}
var mapData map[string]interface{}
if err := json.Unmarshal(jsonDump, &mapData); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("HttpGet error: Unmarshal: %v", err))
}
res = append(res, mapData)
}

return c.JSON(http.StatusOK, res)
}

// Set executes specified Replace/Update/Delete operations and responds what is done by SetRequest.
func HttpSet(c echo.Context, ctx context.Context, gogit *gogit.Git, scfg *ServeCfg) error {
c.Logger().Info("set called for http")
req := echo.Map{}
if err := c.Bind(&req); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Please provide valid credentials")
}

var reqBody HttpSetBody

if _, ok := req["path"]; !ok {
return echo.NewHTTPError(http.StatusBadRequest, "HttpSet error: not found path")
}
if _, ok := req["path"].(string); !ok {
return echo.NewHTTPError(http.StatusBadRequest, "HttpSet error: not string path")
}
if _, ok := req["value"]; !ok {
return echo.NewHTTPError(http.StatusBadRequest, "HttpSet error: not found val")
}
if _, ok := req["value"].(map[string]interface{}); !ok {
return echo.NewHTTPError(http.StatusBadRequest, "HttpSet error: not map val")
}

reqBody.Path = req["path"].(string)
reqBody.Value = req["value"].(map[string]interface{})
inputPath := filepath.Clean(filepath.Join(scfg.ConfigRootPath, reqBody.Path, "input.cue"))
buf, err := os.ReadFile(inputPath)

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression

This path depends on a [user-provided value](1).
if err != nil && !os.IsNotExist(err) {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("HttpSet error: ReadFile: %v", err))
}

cctx := cuecontext.New()
val, err := kcue.NewValueFromBytes(cctx, buf)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("HttpSet error: NewValueFromBytes: %v", err))
}
jsonDump, err := val.MarshalJSON()
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("HttpSet error: MarshalJSON: %v", err))
}
before := make(map[string]interface{}, 0)
if err := json.Unmarshal(jsonDump, &before); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("HttpSet error: Unmarshal: %v", err))
}

expr := kcue.NewAstExpr(reqBody.Value)
inputVal := cctx.BuildExpr(expr)
if inputVal.Err() != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("HttpSet error: BuildExpr: %v", inputVal.Err()))
}

b, err := kcue.FormatCue(inputVal, cue.Final())
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("HttpSet error: MarshalJSON: %v", err))
}

if err := file.WriteFileWithMkdir(inputPath, b); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("HttpSet error: WriteFileWithMkdir: %v", err))
}

sp := kuesta.ServicePath{RootDir: scfg.ConfigRootPath}
if err := gogit.Add(sp.ServiceDirPath(kuesta.ExcludeRoot)); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("HttpSet error: gogit add: %v", err))
}

serviceApplyCfg := ServiceApplyCfg{RootCfg: scfg.RootCfg}
if err := RunServiceApply(ctx, &serviceApplyCfg); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("HttpSet error: RunServiceApply: %v", err))
}

gitCommitCfg := GitCommitCfg{RootCfg: scfg.RootCfg}
if err := RunGitCommit(ctx, &gitCommitCfg); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("HttpSet error: RunGitCommit: %v", err))
}

res := HttpSetRes{
Before: before,
After: reqBody.Value,
}
return c.JSON(http.StatusOK, res)
}