Skip to content

Commit

Permalink
Add countries mapping
Browse files Browse the repository at this point in the history
  • Loading branch information
bastjan committed Apr 13, 2023
1 parent ffb18f5 commit 85d49e9
Show file tree
Hide file tree
Showing 11 changed files with 926 additions and 21 deletions.
4 changes: 4 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,17 @@ dockers:
- "--platform=linux/amd64"
image_templates:
- "ghcr.io/appuio/control-api:v{{ .Version }}-amd64"
extra_files:
- countries.yaml

- goarch: arm64
use: buildx
build_flag_templates:
- "--platform=linux/arm64/v8"
image_templates:
- "ghcr.io/appuio/control-api:v{{ .Version }}-arm64"
extra_files:
- countries.yaml

docker_manifests:
# For prereleases, updating `latest` does not make sense.
Expand Down
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ RUN \
mkdir /.cache && chmod -R g=u /.cache

COPY control-api /usr/local/bin/
COPY countries.yaml .

RUN chmod a+x /usr/local/bin/control-api

Expand Down
12 changes: 8 additions & 4 deletions api.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/appuio/control-api/apiserver/authwrapper"
billingStore "github.com/appuio/control-api/apiserver/billing"
"github.com/appuio/control-api/apiserver/billing/odoostorage"
"github.com/appuio/control-api/apiserver/billing/odoostorage/odoo/odoo8/countries"
orgStore "github.com/appuio/control-api/apiserver/organization"
"github.com/appuio/control-api/apiserver/secretstorage"
"github.com/appuio/control-api/apiserver/user"
Expand Down Expand Up @@ -57,6 +58,7 @@ func APICommand() *cobra.Command {
cmd.Flags().BoolVar(&ob.billingEntityFakeMetadataSupport, "billing-entity-fake-metadata-support", false, "Enable metadata support for the fake storage backend")
cmd.Flags().StringVar(&ob.odoo8URL, "billing-entity-odoo8-url", "http://localhost:8069", "URL of the Odoo instance to use for billing entities")
cmd.Flags().BoolVar(&ob.odoo8DebugTransport, "billing-entity-odoo8-debug-transport", false, "Enable debug logging for the Odoo transport")
cmd.Flags().StringVar(&ob.odoo8CountryListPath, "billing-entity-odoo8-country-list", "countries.yaml", "Path to the country list file in the format of [{name: \"Germany\", code: \"DE\", id: 81},...]")

cmd.Flags().StringVar(&ib.backingNS, "invitation-storage-backing-ns", "default", "Namespace to store invitation secrets in")

Expand All @@ -79,18 +81,20 @@ func APICommand() *cobra.Command {
}

type odooStorageBuilder struct {
billingEntityStorage, odoo8URL string
billingEntityStorage, odoo8URL, odoo8CountryListPath string
billingEntityFakeMetadataSupport, odoo8DebugTransport bool
}

func (o *odooStorageBuilder) Build(s *runtime.Scheme, g genericregistry.RESTOptionsGetter) (rest.Storage, error) {
fmt.Printf("Building storage with options: %#v\n", o)

switch o.billingEntityStorage {
case "fake":
return billingStore.New(odoostorage.NewFakeStorage(o.billingEntityFakeMetadataSupport).(authwrapper.StorageScoper))(s, g)
case "odoo8":
return billingStore.New(odoostorage.NewOdoo8Storage(o.odoo8URL, o.odoo8DebugTransport).(authwrapper.StorageScoper))(s, g)
countryIDs, err := countries.LoadCountryIDs(o.odoo8CountryListPath)
if err != nil {
return nil, err
}
return billingStore.New(odoostorage.NewOdoo8Storage(o.odoo8URL, o.odoo8DebugTransport, countryIDs).(authwrapper.StorageScoper))(s, g)
default:
return nil, fmt.Errorf("unknown billing entity storage: %s", o.billingEntityStorage)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ type OdooCompositeID struct {
Name string
}

// NewCompositeID creates a new, valid OdooCompositeID.
func NewCompositeID(id int, name string) OdooCompositeID {
return OdooCompositeID{
Valid: true,
ID: id,
Name: name,
}
}

// UnmarshalJSON handles deserialization of OdooCompositeID.
func (t *OdooCompositeID) UnmarshalJSON(b []byte) error {
// Odoo returns false (not null) if a field is not set.
Expand Down
32 changes: 32 additions & 0 deletions apiserver/billing/odoostorage/odoo/odoo8/countries/countries.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package countries

import (
"os"

"sigs.k8s.io/yaml"
)

type Country struct {
ID int `yaml:"id"`
Code string `yaml:"code"`
Name string `yaml:"name"`
}

func LoadCountryIDs(path string) (map[string]int, error) {
r, err := os.ReadFile(path)
if err != nil {
return nil, err
}

var countries []Country
if err := yaml.UnmarshalStrict(r, &countries); err != nil {
return nil, err
}

countryIDs := make(map[string]int, len(countries))
for _, c := range countries {
countryIDs[c.Name] = c.ID
}

return countryIDs, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package countries

import (
"os"
"testing"

"github.com/stretchr/testify/require"
)

func Test_LoadCountryIDs(t *testing.T) {
const countriesYAML = `
- id: 1
code: BE
name: Belgium
- id: 5
code: FR
name: France
- id: 6
code: false # Seen in the export of the Odoo database
name: Kabott
`

td := t.TempDir()
p := td + "/countries.yaml"
require.NoError(t, os.WriteFile(p, []byte(countriesYAML), 0644))

countryIDs, err := LoadCountryIDs(p)
require.NoError(t, err)
require.Equal(t, map[string]int{
"Belgium": 1,
"France": 5,
"Kabott": 6,
}, countryIDs)
}
40 changes: 25 additions & 15 deletions apiserver/billing/odoostorage/odoo/odoo8/odoo8.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,18 @@ var accountingContactUpdateAllowedFields = newSet(
"email",
)

func NewOdoo8Storage(odooURL string, debugTransport bool) odoo.OdooStorage {
func NewOdoo8Storage(odooURL string, debugTransport bool, countryIDs map[string]int) odoo.OdooStorage {
return &oodo8Storage{
countryIDs: countryIDs,
sessionCreator: func(ctx context.Context) (client.QueryExecutor, error) {
return client.Open(ctx, odooURL, client.ClientOptions{UseDebugLogger: debugTransport})
},
}
}

type oodo8Storage struct {
countryIDs map[string]int

sessionCreator func(ctx context.Context) (client.QueryExecutor, error)
}

Expand Down Expand Up @@ -162,7 +165,10 @@ func (s *oodo8Storage) Create(ctx context.Context, be *billingv1.BillingEntity)
if be == nil {
return errors.New("billing entity is nil")
}
company, accounting := mapBillingEntityToPartners(*be)
company, accounting, err := mapBillingEntityToPartners(*be, s.countryIDs)
if err != nil {
return fmt.Errorf("failed mapping billing entity to partners: %w", err)
}

inflight := uuid.New().String()
l = l.WithValues("debug_inflight", inflight)
Expand Down Expand Up @@ -212,7 +218,11 @@ func (s *oodo8Storage) Update(ctx context.Context, be *billingv1.BillingEntity)
return errors.New("billing entity is nil")
}

company, accounting := mapBillingEntityToPartners(*be)
company, accounting, err := mapBillingEntityToPartners(*be, s.countryIDs)
if err != nil {
return fmt.Errorf("failed mapping billing entity to partners: %w", err)
}

origCompany, origAccounting, err := s.get(ctx, be.Name)
if err != nil {
return fmt.Errorf("error fetching billing entity to update: %w", err)
Expand Down Expand Up @@ -297,21 +307,21 @@ func mapPartnersToBillingEntity(company model.Partner, accounting model.Partner)
}
}

func mapBillingEntityToPartners(be billingv1.BillingEntity) (company model.Partner, accounting model.Partner) {
func mapBillingEntityToPartners(be billingv1.BillingEntity, countryIDs map[string]int) (company model.Partner, accounting model.Partner, err error) {
countryID, ok := countryIDs[be.Spec.Address.Country]
if !ok {
return company, accounting, fmt.Errorf("unknown country %q", be.Spec.Address.Country)
}

company = model.Partner{
Name: be.Spec.Name,
Phone: model.NewNullable(be.Spec.Phone),

Street: model.NewNullable(be.Spec.Address.Line1),
Street2: model.NewNullable(be.Spec.Address.Line2),
City: model.NewNullable(be.Spec.Address.City),
Zip: model.NewNullable(be.Spec.Address.PostalCode),
CountryID: model.OdooCompositeID{
ID: 256, // TODO(swi): Switzerland is hardcoded for now
Valid: true,
// Name: be.Spec.Address.Country,
Name: "Switzerland",
},
Street: model.NewNullable(be.Spec.Address.Line1),
Street2: model.NewNullable(be.Spec.Address.Line2),
City: model.NewNullable(be.Spec.Address.City),
Zip: model.NewNullable(be.Spec.Address.PostalCode),
CountryID: model.NewCompositeID(countryID, ""),
}
company.SetEmails(be.Spec.Emails)

Expand All @@ -320,7 +330,7 @@ func mapBillingEntityToPartners(be billingv1.BillingEntity) (company model.Partn
}
accounting.SetEmails(be.Spec.AccountingContact.Emails)

return company, accounting
return company, accounting, nil
}

func setStaticAccountingContactFields(a *model.Partner) {
Expand Down
19 changes: 19 additions & 0 deletions apiserver/billing/odoostorage/odoo/odoo8/odoo8_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -327,11 +327,30 @@ func TestUpdate(t *testing.T) {
}, s)
}

func Test_CreateUpdate_UnknownCountry(t *testing.T) {
ctrl, _, subject := createStorage(t)
defer ctrl.Finish()

s := &billingv1.BillingEntity{
ObjectMeta: metav1.ObjectMeta{
Name: "be-702",
},
Spec: billingv1.BillingEntitySpec{
Address: billingv1.BillingEntityAddress{
Country: "Vatican City",
},
},
}
require.ErrorContains(t, subject.Create(context.Background(), s), "unknown country")
require.ErrorContains(t, subject.Update(context.Background(), s), "unknown country")
}

func createStorage(t *testing.T) (*gomock.Controller, *clientmock.MockQueryExecutor, *oodo8Storage) {
ctrl := gomock.NewController(t)
mock := clientmock.NewMockQueryExecutor(ctrl)

return ctrl, mock, &oodo8Storage{
countryIDs: map[string]int{"": 0, "Switzerland": 1, "Germany": 2},
sessionCreator: func(ctx context.Context) (client.QueryExecutor, error) {
return mock, nil
},
Expand Down
4 changes: 2 additions & 2 deletions apiserver/billing/odoostorage/odoostorage.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ func NewFakeStorage(metadataSupport bool) Storage {
}

// NewOdoo8Storage returns a new storage provider for BillingEntities
func NewOdoo8Storage(odooURL string, debugTransport bool) Storage {
func NewOdoo8Storage(odooURL string, debugTransport bool, countryIDs map[string]int) Storage {
return &billingEntityStorage{
storage: odoo8.NewOdoo8Storage(odooURL, debugTransport),
storage: odoo8.NewOdoo8Storage(odooURL, debugTransport, countryIDs),
}
}

Expand Down
Loading

0 comments on commit 85d49e9

Please sign in to comment.