Bindings refactor

this is step one of refactoring our golang binaries.  we will no be
using structs to pass optional options.  required options will still
arguments to the binding itself.

the structs then have a generator to create helper functions which
should then be added to the git repo.

Signed-off-by: baude <bbaude@redhat.com>
This commit is contained in:
baude
2020-12-07 10:54:54 -06:00
parent 2bb149034b
commit ead8b5be0f
6 changed files with 351 additions and 13 deletions

View File

@ -0,0 +1,234 @@
package main
import (
"errors"
"fmt"
"go/ast"
"go/parser"
"go/token"
"io/ioutil"
"os"
"os/exec"
"strings"
"text/template"
"time"
)
var bodyTmpl = `package {{.PackageName}}
import (
{{range $import := .Imports}} {{$import}}
{{end}}
)
/*
This file is generated automatically by go generate. Do not edit.
Created {{.Date}}
*/
// Changed
func (o *{{.StructName}}) Changed(fieldName string) bool {
r := reflect.ValueOf(o)
value := reflect.Indirect(r).FieldByName(fieldName)
return !value.IsNil()
}
// ToParams
func (o *{{.StructName}}) ToParams() (url.Values, error) {
params := url.Values{}
if o == nil {
return params, nil
}
json := jsoniter.ConfigCompatibleWithStandardLibrary
s := reflect.ValueOf(o)
if reflect.Ptr == s.Kind() {
s = s.Elem()
}
sType := s.Type()
for i := 0; i < s.NumField(); i++ {
fieldName := sType.Field(i).Name
if !o.Changed(fieldName) {
continue
}
f := s.Field(i)
if reflect.Ptr == f.Kind() {
f = f.Elem()
}
switch f.Kind() {
case reflect.Bool:
params.Set(fieldName, strconv.FormatBool(f.Bool()))
case reflect.String:
params.Set(fieldName, f.String())
case reflect.Int, reflect.Int64:
// f.Int() is always an int64
params.Set(fieldName, strconv.FormatInt(f.Int(), 10))
case reflect.Slice:
typ := reflect.TypeOf(f.Interface()).Elem()
slice := reflect.MakeSlice(reflect.SliceOf(typ), f.Len(), f.Cap())
switch typ.Kind() {
case reflect.String:
s, ok := slice.Interface().([]string)
if !ok {
return nil, errors.New("failed to convert to string slice")
}
for _, val := range s {
params.Add(fieldName, val)
}
default:
return nil, errors.Errorf("unknown slice type %s", f.Kind().String())
}
case reflect.Map:
lowerCaseKeys := make(map[string][]string)
// I dont know if this code is needed anymore, TBD
// for k, v := range filters {
// lowerCaseKeys[strings.ToLower(k)] = v
// }
s, err := json.MarshalToString(lowerCaseKeys)
if err != nil {
return nil, err
}
params.Set(fieldName, s)
default:
return nil, errors.Errorf("unknown type %s", f.Kind().String())
}
}
return params, nil
}
`
var fieldTmpl = `
// With{{.Name}}
func(o *{{.StructName}}) With{{.Name}}(value {{.Type}}) *{{.StructName}} {
v := &value
o.{{.Name}} = v
return o
}
`
type fieldStruct struct {
Name string
StructName string
Type string
}
func main() {
var (
closed bool
fieldStructs []fieldStruct
structNode ast.Node
)
srcFile := os.Getenv("GOFILE")
pkg := os.Getenv("GOPACKAGE")
inputStructName := os.Args[1]
b, err := ioutil.ReadFile(srcFile)
if err != nil {
panic(err)
}
fset := token.NewFileSet() // positions are relative to fset
f, err := parser.ParseFile(fset, "", b, parser.ParseComments)
if err != nil {
panic(err)
}
// always add reflect
imports := []string{"\"reflect\""}
for _, imp := range f.Imports {
imports = append(imports, imp.Path.Value)
}
out, err := os.Create(strings.ToLower(inputStructName) + "_" + srcFile)
if err != nil {
panic(err)
}
defer func() {
if !closed {
out.Close()
}
}()
bodyStruct := struct {
PackageName string
Imports []string
Date string
StructName string
}{
PackageName: pkg,
Imports: imports,
Date: time.Now().String(),
StructName: inputStructName,
}
body := template.Must(template.New("body").Parse(bodyTmpl))
fields := template.Must(template.New("fields").Parse(fieldTmpl))
ast.Inspect(f, func(n ast.Node) bool {
ref, refOK := n.(*ast.TypeSpec)
if refOK {
if ref.Name.Name == inputStructName {
structNode = n
x := ref.Type.(*ast.StructType)
for _, field := range x.Fields.List {
var (
name string
)
typeExpr := field.Type
start := typeExpr.Pos() - 1
end := typeExpr.End() - 1
fieldType := strings.Replace(string(b[start:end]), "*", "", 1)
if len(field.Names) > 0 {
name = field.Names[0].Name
if len(name) < 1 {
panic(errors.New("bad name"))
}
}
fStruct := fieldStruct{
Name: name,
StructName: inputStructName,
Type: fieldType,
}
fieldStructs = append(fieldStructs, fStruct)
} // for
// create the body
if err := body.Execute(out, bodyStruct); err != nil {
fmt.Println(err)
os.Exit(1)
}
// create with func from the struct fields
for _, fs := range fieldStructs {
if err := fields.Execute(out, fs); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
// close out file
if err := out.Close(); err != nil {
fmt.Println(err)
os.Exit(1)
}
closed = true
// go fmt file
gofmt := exec.Command("gofmt", "-w", "-s", out.Name())
gofmt.Stderr = os.Stdout
if err := gofmt.Run(); err != nil {
fmt.Println(err)
os.Exit(1)
}
// go import file
goimport := exec.Command("goimports", "-w", out.Name())
goimport.Stderr = os.Stdout
if err := goimport.Run(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
}
return true
})
}

View File

@ -0,0 +1,93 @@
package images
import (
"net/url"
"reflect"
"strconv"
jsoniter "github.com/json-iterator/go"
"github.com/pkg/errors"
)
/*
This file is generated automatically by go generate. Do not edit.
Created 2020-12-10 12:51:06.090426622 -0600 CST m=+0.000133169
*/
// Changed
func (o *RemoveOptions) Changed(fieldName string) bool {
r := reflect.ValueOf(o)
value := reflect.Indirect(r).FieldByName(fieldName)
return !value.IsNil()
}
// ToParams
func (o *RemoveOptions) ToParams() (url.Values, error) {
params := url.Values{}
if o == nil {
return params, nil
}
json := jsoniter.ConfigCompatibleWithStandardLibrary
s := reflect.ValueOf(o)
if reflect.Ptr == s.Kind() {
s = s.Elem()
}
sType := s.Type()
for i := 0; i < s.NumField(); i++ {
fieldName := sType.Field(i).Name
if !o.Changed(fieldName) {
continue
}
f := s.Field(i)
if reflect.Ptr == f.Kind() {
f = f.Elem()
}
switch f.Kind() {
case reflect.Bool:
params.Set(fieldName, strconv.FormatBool(f.Bool()))
case reflect.String:
params.Set(fieldName, f.String())
case reflect.Int, reflect.Int64:
// f.Int() is always an int64
params.Set(fieldName, strconv.FormatInt(f.Int(), 10))
case reflect.Slice:
typ := reflect.TypeOf(f.Interface()).Elem()
slice := reflect.MakeSlice(reflect.SliceOf(typ), f.Len(), f.Cap())
switch typ.Kind() {
case reflect.String:
s, ok := slice.Interface().([]string)
if !ok {
return nil, errors.New("failed to convert to string slice")
}
for _, val := range s {
params.Add(fieldName, val)
}
default:
return nil, errors.Errorf("unknown slice type %s", f.Kind().String())
}
case reflect.Map:
lowerCaseKeys := make(map[string][]string)
// I dont know if this code is needed anymore, TBD
// for k, v := range filters {
// lowerCaseKeys[strings.ToLower(k)] = v
// }
s, err := json.MarshalToString(lowerCaseKeys)
if err != nil {
return nil, err
}
params.Set(fieldName, s)
default:
return nil, errors.Errorf("unknown type %s", f.Kind().String())
}
}
return params, nil
}
// WithForce
func (o *RemoveOptions) WithForce(value bool) *RemoveOptions {
v := &value
o.Force = v
return o
}

View File

@ -41,17 +41,19 @@ func BatchRemove(ctx context.Context, images []string, opts entities.ImageRemove
return &report.ImageRemoveReport, errorhandling.StringsToErrors(report.Errors)
}
// Remove removes an image from the local storage. Use force to remove an
// Remove removes an image from the local storage. Use optional force option to remove an
// image, even if it's used by containers.
func Remove(ctx context.Context, nameOrID string, force bool) (*entities.ImageRemoveReport, error) {
func Remove(ctx context.Context, nameOrID string, options *RemoveOptions) (*entities.ImageRemoveReport, error) {
var report handlers.LibpodImagesRemoveReport
conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
params := url.Values{}
params.Set("force", strconv.FormatBool(force))
params, err := options.ToParams()
if err != nil {
return nil, err
}
response, err := conn.DoRequest(nil, http.MethodDelete, "/images/%s", params, nil, nameOrID)
if err != nil {
return nil, err

View File

@ -0,0 +1,8 @@
package images
//go:generate go run ../generator/generator.go RemoveOptions
// RemoveOptions are optional options for image removal
type RemoveOptions struct {
// Forces removes all containers based on the image
Force *bool
}

View File

@ -84,7 +84,7 @@ var _ = Describe("Podman images", func() {
// Test to validate the remove image api
It("remove image", func() {
// Remove invalid image should be a 404
response, err := images.Remove(bt.conn, "foobar5000", false)
response, err := images.Remove(bt.conn, "foobar5000", nil)
Expect(err).ToNot(BeNil())
Expect(response).To(BeNil())
code, _ := bindings.CheckResponseCode(err)
@ -93,7 +93,7 @@ var _ = Describe("Podman images", func() {
// Remove an image by name, validate image is removed and error is nil
inspectData, err := images.GetImage(bt.conn, busybox.shortName, nil)
Expect(err).To(BeNil())
response, err = images.Remove(bt.conn, busybox.shortName, false)
response, err = images.Remove(bt.conn, busybox.shortName, nil)
Expect(err).To(BeNil())
code, _ = bindings.CheckResponseCode(err)
@ -113,12 +113,13 @@ var _ = Describe("Podman images", func() {
// try to remove the image "alpine". This should fail since we are not force
// deleting hence image cannot be deleted until the container is deleted.
response, err = images.Remove(bt.conn, alpine.shortName, false)
response, err = images.Remove(bt.conn, alpine.shortName, nil)
code, _ = bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusConflict))
// Removing the image "alpine" where force = true
response, err = images.Remove(bt.conn, alpine.shortName, true)
options := images.RemoveOptions{}
response, err = images.Remove(bt.conn, alpine.shortName, options.WithForce(true))
Expect(err).To(BeNil())
// To be extra sure, check if the previously created container
// is gone as well.
@ -213,7 +214,7 @@ var _ = Describe("Podman images", func() {
It("Load|Import Image", func() {
// load an image
_, err := images.Remove(bt.conn, alpine.name, false)
_, err := images.Remove(bt.conn, alpine.name, nil)
Expect(err).To(BeNil())
exists, err := images.Exists(bt.conn, alpine.name)
Expect(err).To(BeNil())
@ -231,7 +232,7 @@ var _ = Describe("Podman images", func() {
// load with a repo name
f, err = os.Open(filepath.Join(ImageCacheDir, alpine.tarballName))
Expect(err).To(BeNil())
_, err = images.Remove(bt.conn, alpine.name, false)
_, err = images.Remove(bt.conn, alpine.name, nil)
Expect(err).To(BeNil())
exists, err = images.Exists(bt.conn, alpine.name)
Expect(err).To(BeNil())
@ -247,7 +248,7 @@ var _ = Describe("Podman images", func() {
// load with a bad repo name should trigger a 500
f, err = os.Open(filepath.Join(ImageCacheDir, alpine.tarballName))
Expect(err).To(BeNil())
_, err = images.Remove(bt.conn, alpine.name, false)
_, err = images.Remove(bt.conn, alpine.name, nil)
Expect(err).To(BeNil())
exists, err = images.Exists(bt.conn, alpine.name)
Expect(err).To(BeNil())
@ -275,7 +276,7 @@ var _ = Describe("Podman images", func() {
It("Import Image", func() {
// load an image
_, err = images.Remove(bt.conn, alpine.name, false)
_, err = images.Remove(bt.conn, alpine.name, nil)
Expect(err).To(BeNil())
exists, err := images.Exists(bt.conn, alpine.name)
Expect(err).To(BeNil())

View File

@ -47,7 +47,7 @@ var _ = Describe("Podman containers ", func() {
code, _ := bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
_, err = images.Remove(bt.conn, id, false)
_, err = images.Remove(bt.conn, id, nil)
Expect(err).To(BeNil())
// create manifest list with images