diff --git a/Makefile b/Makefile index d0e83da27..973cf95db 100644 --- a/Makefile +++ b/Makefile @@ -59,12 +59,3 @@ update-gomod: # Use the bundled toolchain that meets the minimum go version go get toolchain@none - -# Update lxd-generate generated database helpers. -.PHONY: update-schema -update-schema: - go generate ./cluster/... - gofmt -s -w ./cluster/ - goimports -w ./cluster/ - @echo "Code generation completed" - diff --git a/client/client.go b/client/client.go index 9c2d964f9..c2c1f9ef1 100644 --- a/client/client.go +++ b/client/client.go @@ -8,7 +8,7 @@ import ( "github.com/gorilla/websocket" "github.com/canonical/microcluster/v3/internal/rest/client" - "github.com/canonical/microcluster/v3/rest/types" + "github.com/canonical/microcluster/v3/microcluster/types" ) // Client is a rest client for the microcluster daemon. diff --git a/cluster/schema.go b/cluster/schema.go deleted file mode 100644 index 2cd4e5663..000000000 --- a/cluster/schema.go +++ /dev/null @@ -1,12 +0,0 @@ -package cluster - -import ( - "time" -) - -// Schema represents the database schema table. -type Schema struct { - ID int - Version int `db:"primary=yes"` - UpdatedAt time.Time -} diff --git a/example/Makefile b/example/Makefile index 892e1525d..9e5246a8e 100644 --- a/example/Makefile +++ b/example/Makefile @@ -50,12 +50,3 @@ endif update-gomod: go get -u ./... go mod tidy - -# Update lxd-generate generated database helpers. -.PHONY: update-schema -update-schema: - go generate ./... - gofmt -s -w ./database/ - goimports -w ./database/ - @echo "Code generation completed" - diff --git a/example/api/extended.go b/example/api/extended.go index ce429caf1..38760747d 100644 --- a/example/api/extended.go +++ b/example/api/extended.go @@ -13,9 +13,9 @@ import ( "github.com/canonical/microcluster/v3/client" extendedTypes "github.com/canonical/microcluster/v3/example/api/types" extendedClient "github.com/canonical/microcluster/v3/example/client" - "github.com/canonical/microcluster/v3/rest" - "github.com/canonical/microcluster/v3/rest/response" - "github.com/canonical/microcluster/v3/rest/types" + "github.com/canonical/microcluster/v3/microcluster/rest" + "github.com/canonical/microcluster/v3/microcluster/rest/response" + "github.com/canonical/microcluster/v3/microcluster/types" "github.com/canonical/microcluster/v3/state" ) diff --git a/example/api/servers.go b/example/api/servers.go index 8fb421b54..e98ad52f6 100644 --- a/example/api/servers.go +++ b/example/api/servers.go @@ -3,7 +3,7 @@ package api import ( "github.com/canonical/microcluster/v3/example/api/types" - "github.com/canonical/microcluster/v3/rest" + "github.com/canonical/microcluster/v3/microcluster/rest" ) // Servers represents the list of listeners that the daemon will start diff --git a/example/api/types/extended.go b/example/api/types/extended.go index 7991d5169..a8f05d73a 100644 --- a/example/api/types/extended.go +++ b/example/api/types/extended.go @@ -1,7 +1,7 @@ // Package types provides shared types and structs. package types -import "github.com/canonical/microcluster/v3/rest/types" +import "github.com/canonical/microcluster/v3/microcluster/types" // ExtendedType is an example of an API type usable by MicroCluster but defined by this example project. type ExtendedType struct { diff --git a/example/api/types/server.go b/example/api/types/server.go index 86c7c86a2..b477a0de0 100644 --- a/example/api/types/server.go +++ b/example/api/types/server.go @@ -1,6 +1,6 @@ package types -import "github.com/canonical/microcluster/v3/rest/types" +import "github.com/canonical/microcluster/v3/microcluster/types" const ( // ExtendedPathPrefix is the path prefix that will be used for the extended endpoints. diff --git a/example/cmd/microctl/cluster_members.go b/example/cmd/microctl/cluster_members.go index 9808c283f..cea9f4817 100644 --- a/example/cmd/microctl/cluster_members.go +++ b/example/cmd/microctl/cluster_members.go @@ -18,8 +18,8 @@ import ( "gopkg.in/yaml.v3" "github.com/canonical/microcluster/v3/client" - "github.com/canonical/microcluster/v3/cluster" "github.com/canonical/microcluster/v3/microcluster" + "github.com/canonical/microcluster/v3/microcluster/types" ) const recoveryConfirmation = `You should only run this command if: @@ -254,7 +254,7 @@ func (c *cmdClusterEdit) run(cmd *cobra.Command, args []string) error { } } - newMembers := []cluster.DqliteMember{} + newMembers := []types.DqliteMember{} err = yaml.Unmarshal(content, &newMembers) if err != nil { return err diff --git a/example/cmd/microd/main.go b/example/cmd/microd/main.go index 223d7ffd3..1fd8cf544 100644 --- a/example/cmd/microd/main.go +++ b/example/cmd/microd/main.go @@ -14,7 +14,7 @@ import ( "github.com/canonical/microcluster/v3/example/database" "github.com/canonical/microcluster/v3/example/version" "github.com/canonical/microcluster/v3/microcluster" - "github.com/canonical/microcluster/v3/rest/types" + "github.com/canonical/microcluster/v3/microcluster/types" "github.com/canonical/microcluster/v3/state" ) diff --git a/example/database/extended.mapper.go b/example/database/extended.mapper.go index d8a3aade3..4ba3f7da1 100644 --- a/example/database/extended.mapper.go +++ b/example/database/extended.mapper.go @@ -10,47 +10,45 @@ import ( "github.com/canonical/lxd/shared/api" - "github.com/canonical/microcluster/v3/cluster" - "github.com/canonical/microcluster/v3/cluster/db" + "github.com/canonical/microcluster/v3/microcluster/db" ) var _ = api.ServerEnvironment{} -var extendedTableObjects = cluster.RegisterStmt(` +var extendedTableObjects = db.RegisterStmt(` SELECT extended_table.id, extended_table.key, extended_table.value FROM extended_table ORDER BY extended_table.key `) -var extendedTableObjectsByKey = cluster.RegisterStmt(` +var extendedTableObjectsByKey = db.RegisterStmt(` SELECT extended_table.id, extended_table.key, extended_table.value FROM extended_table WHERE ( extended_table.key = ? ) ORDER BY extended_table.key `) -var extendedTableID = cluster.RegisterStmt(` +var extendedTableID = db.RegisterStmt(` SELECT extended_table.id FROM extended_table WHERE extended_table.key = ? `) -var extendedTableCreate = cluster.RegisterStmt(` +var extendedTableCreate = db.RegisterStmt(` INSERT INTO extended_table (key, value) VALUES (?, ?) `) -var extendedTableDeleteByKey = cluster.RegisterStmt(` +var extendedTableDeleteByKey = db.RegisterStmt(` DELETE FROM extended_table WHERE key = ? `) -var extendedTableUpdate = cluster.RegisterStmt(` +var extendedTableUpdate = db.RegisterStmt(` UPDATE extended_table SET key = ?, value = ? WHERE id = ? `) // GetExtendedTables returns all available extended_tables. -// generator: extended_table GetMany func GetExtendedTables(ctx context.Context, tx *sql.Tx, filters ...ExtendedTableFilter) ([]ExtendedTable, error) { var err error @@ -63,7 +61,7 @@ func GetExtendedTables(ctx context.Context, tx *sql.Tx, filters ...ExtendedTable queryParts := [2]string{} if len(filters) == 0 { - sqlStmt, err = cluster.Stmt(tx, extendedTableObjects) + sqlStmt, err = db.Stmt(tx, extendedTableObjects) if err != nil { return nil, fmt.Errorf("Failed to get \"extendedTableObjects\" prepared statement: %w", err) } @@ -73,7 +71,7 @@ func GetExtendedTables(ctx context.Context, tx *sql.Tx, filters ...ExtendedTable if filter.Key != nil { args = append(args, []any{filter.Key}...) if len(filters) == 1 { - sqlStmt, err = cluster.Stmt(tx, extendedTableObjectsByKey) + sqlStmt, err = db.Stmt(tx, extendedTableObjectsByKey) if err != nil { return nil, fmt.Errorf("Failed to get \"extendedTableObjectsByKey\" prepared statement: %w", err) } @@ -81,7 +79,7 @@ func GetExtendedTables(ctx context.Context, tx *sql.Tx, filters ...ExtendedTable break } - query, err := cluster.StmtString(extendedTableObjectsByKey) + query, err := db.StmtString(extendedTableObjectsByKey) if err != nil { return nil, fmt.Errorf("Failed to get \"extendedTableObjects\" prepared statement: %w", err) } @@ -130,7 +128,6 @@ func GetExtendedTables(ctx context.Context, tx *sql.Tx, filters ...ExtendedTable } // GetExtendedTable returns the extended_table with the given key. -// generator: extended_table GetOne func GetExtendedTable(ctx context.Context, tx *sql.Tx, key string) (*ExtendedTable, error) { filter := ExtendedTableFilter{} filter.Key = &key @@ -151,9 +148,8 @@ func GetExtendedTable(ctx context.Context, tx *sql.Tx, key string) (*ExtendedTab } // GetExtendedTableID return the ID of the extended_table with the given key. -// generator: extended_table ID func GetExtendedTableID(ctx context.Context, tx *sql.Tx, key string) (int64, error) { - stmt, err := cluster.Stmt(tx, extendedTableID) + stmt, err := db.Stmt(tx, extendedTableID) if err != nil { return -1, fmt.Errorf("Failed to get \"extendedTableID\" prepared statement: %w", err) } @@ -173,7 +169,6 @@ func GetExtendedTableID(ctx context.Context, tx *sql.Tx, key string) (int64, err } // ExtendedTableExists checks if a extended_table with the given key exists. -// generator: extended_table Exists func ExtendedTableExists(ctx context.Context, tx *sql.Tx, key string) (bool, error) { _, err := GetExtendedTableID(ctx, tx, key) if err != nil { @@ -188,7 +183,6 @@ func ExtendedTableExists(ctx context.Context, tx *sql.Tx, key string) (bool, err } // CreateExtendedTable adds a new extended_table to the database. -// generator: extended_table Create func CreateExtendedTable(ctx context.Context, tx *sql.Tx, object ExtendedTable) (int64, error) { // Check if a extended_table with the same key exists. exists, err := ExtendedTableExists(ctx, tx, object.Key) @@ -207,7 +201,7 @@ func CreateExtendedTable(ctx context.Context, tx *sql.Tx, object ExtendedTable) args[1] = object.Value // Prepared statement to use. - stmt, err := cluster.Stmt(tx, extendedTableCreate) + stmt, err := db.Stmt(tx, extendedTableCreate) if err != nil { return -1, fmt.Errorf("Failed to get \"extendedTableCreate\" prepared statement: %w", err) } @@ -227,9 +221,8 @@ func CreateExtendedTable(ctx context.Context, tx *sql.Tx, object ExtendedTable) } // DeleteExtendedTable deletes the extended_table matching the given key parameters. -// generator: extended_table DeleteOne-by-Key func DeleteExtendedTable(ctx context.Context, tx *sql.Tx, key string) error { - stmt, err := cluster.Stmt(tx, extendedTableDeleteByKey) + stmt, err := db.Stmt(tx, extendedTableDeleteByKey) if err != nil { return fmt.Errorf("Failed to get \"extendedTableDeleteByKey\" prepared statement: %w", err) } @@ -254,14 +247,13 @@ func DeleteExtendedTable(ctx context.Context, tx *sql.Tx, key string) error { } // UpdateExtendedTable updates the extended_table matching the given key parameters. -// generator: extended_table Update func UpdateExtendedTable(ctx context.Context, tx *sql.Tx, key string, object ExtendedTable) error { id, err := GetExtendedTableID(ctx, tx, key) if err != nil { return err } - stmt, err := cluster.Stmt(tx, extendedTableUpdate) + stmt, err := db.Stmt(tx, extendedTableUpdate) if err != nil { return fmt.Errorf("Failed to get \"extendedTableUpdate\" prepared statement: %w", err) } diff --git a/example/database/extended_schema.go b/example/database/extended_schema.go index 427f14d15..83fff6f3a 100644 --- a/example/database/extended_schema.go +++ b/example/database/extended_schema.go @@ -5,7 +5,7 @@ import ( "context" "database/sql" - "github.com/canonical/microcluster/v3/cluster/db" + "github.com/canonical/microcluster/v3/microcluster/db" ) // SchemaExtensions is a list of schema extensions that can be passed to the MicroCluster daemon. diff --git a/example/database/extended_table.go b/example/database/extended_table.go index b6d588d02..90e45f497 100644 --- a/example/database/extended_table.go +++ b/example/database/extended_table.go @@ -1,32 +1,13 @@ package database -//go:generate -command mapper lxd-generate db mapper -t extended.mapper.go -//go:generate mapper reset -// -//go:generate mapper stmt -d github.com/canonical/microcluster/v3/cluster -e extended_table objects table=extended_table -//go:generate mapper stmt -d github.com/canonical/microcluster/v3/cluster -e extended_table objects-by-Key table=extended_table -//go:generate mapper stmt -d github.com/canonical/microcluster/v3/cluster -e extended_table id table=extended_table -//go:generate mapper stmt -d github.com/canonical/microcluster/v3/cluster -e extended_table create table=extended_table -//go:generate mapper stmt -d github.com/canonical/microcluster/v3/cluster -e extended_table delete-by-Key table=extended_table -//go:generate mapper stmt -d github.com/canonical/microcluster/v3/cluster -e extended_table update table=extended_table -// -//go:generate mapper method -i -d github.com/canonical/microcluster/v3/cluster -e extended_table GetMany table=extended_table -//go:generate mapper method -i -d github.com/canonical/microcluster/v3/cluster -e extended_table GetOne table=extended_table -//go:generate mapper method -i -d github.com/canonical/microcluster/v3/cluster -e extended_table ID table=extended_table -//go:generate mapper method -i -d github.com/canonical/microcluster/v3/cluster -e extended_table Exists table=extended_table -//go:generate mapper method -i -d github.com/canonical/microcluster/v3/cluster -e extended_table Create table=extended_table -//go:generate mapper method -i -d github.com/canonical/microcluster/v3/cluster -e extended_table DeleteOne-by-Key table=extended_table -//go:generate mapper method -i -d github.com/canonical/microcluster/v3/cluster -e extended_table Update table=extended_table - -// ExtendedTable is an example of a database table. In this case named `extended_table`. The above comments will -// generate database queries and helpers using lxd-generate. +// ExtendedTable is an example of a database table. In this case named `extended_table`. type ExtendedTable struct { ID int Key string `db:"primary=yes"` Value string } -// ExtendedTableFilter is a required struct for use with lxd-generate. It is used for filtering fields on database +// ExtendedTableFilter is used for filtering fields on database // fetches. In this case we will only support filtering by Key. type ExtendedTableFilter struct { Key *string diff --git a/cluster/cluster_members.go b/internal/cluster/cluster_members.go similarity index 74% rename from cluster/cluster_members.go rename to internal/cluster/cluster_members.go index eeb69554c..af810741e 100644 --- a/cluster/cluster_members.go +++ b/internal/cluster/cluster_members.go @@ -8,28 +8,9 @@ import ( "github.com/canonical/microcluster/v3/internal/db/update" "github.com/canonical/microcluster/v3/internal/extensions" - "github.com/canonical/microcluster/v3/rest/types" + "github.com/canonical/microcluster/v3/microcluster/types" ) -//go:generate -command mapper lxd-generate db mapper -t cluster_members.mapper.go -//go:generate mapper reset -// -//go:generate mapper stmt -e core_cluster_member objects table=core_cluster_members -//go:generate mapper stmt -e core_cluster_member objects-by-Address table=core_cluster_members -//go:generate mapper stmt -e core_cluster_member objects-by-Name table=core_cluster_members -//go:generate mapper stmt -e core_cluster_member id table=core_cluster_members -//go:generate mapper stmt -e core_cluster_member create table=core_cluster_members -//go:generate mapper stmt -e core_cluster_member delete-by-Address table=core_cluster_members -//go:generate mapper stmt -e core_cluster_member update table=core_cluster_members -// -//go:generate mapper method -i -e core_cluster_member GetMany table=core_cluster_members -//go:generate mapper method -i -e core_cluster_member GetOne table=core_cluster_members -//go:generate mapper method -i -e core_cluster_member ID table=core_cluster_members -//go:generate mapper method -i -e core_cluster_member Exists table=core_cluster_members -//go:generate mapper method -i -e core_cluster_member Create table=core_cluster_members -//go:generate mapper method -i -e core_cluster_member DeleteOne-by-Address table=core_cluster_members -//go:generate mapper method -i -e core_cluster_member Update table=core_cluster_members - // Role is the role of the dqlite cluster member. type Role string diff --git a/cluster/cluster_members.mapper.go b/internal/cluster/cluster_members.mapper.go similarity index 88% rename from cluster/cluster_members.mapper.go rename to internal/cluster/cluster_members.mapper.go index 6647e80d2..610d99eeb 100644 --- a/cluster/cluster_members.mapper.go +++ b/internal/cluster/cluster_members.mapper.go @@ -10,46 +10,46 @@ import ( "github.com/canonical/lxd/shared/api" - "github.com/canonical/microcluster/v3/cluster/db" + clusterDB "github.com/canonical/microcluster/v3/microcluster/db" ) var _ = api.ServerEnvironment{} -var coreClusterMemberObjects = RegisterStmt(` +var coreClusterMemberObjects = clusterDB.RegisterStmt(` SELECT core_cluster_members.id, core_cluster_members.name, core_cluster_members.address, core_cluster_members.certificate, core_cluster_members.schema_internal, core_cluster_members.schema_external, core_cluster_members.api_extensions, core_cluster_members.heartbeat, core_cluster_members.role FROM core_cluster_members ORDER BY core_cluster_members.name `) -var coreClusterMemberObjectsByAddress = RegisterStmt(` +var coreClusterMemberObjectsByAddress = clusterDB.RegisterStmt(` SELECT core_cluster_members.id, core_cluster_members.name, core_cluster_members.address, core_cluster_members.certificate, core_cluster_members.schema_internal, core_cluster_members.schema_external, core_cluster_members.api_extensions, core_cluster_members.heartbeat, core_cluster_members.role FROM core_cluster_members WHERE ( core_cluster_members.address = ? ) ORDER BY core_cluster_members.name `) -var coreClusterMemberObjectsByName = RegisterStmt(` +var coreClusterMemberObjectsByName = clusterDB.RegisterStmt(` SELECT core_cluster_members.id, core_cluster_members.name, core_cluster_members.address, core_cluster_members.certificate, core_cluster_members.schema_internal, core_cluster_members.schema_external, core_cluster_members.api_extensions, core_cluster_members.heartbeat, core_cluster_members.role FROM core_cluster_members WHERE ( core_cluster_members.name = ? ) ORDER BY core_cluster_members.name `) -var coreClusterMemberID = RegisterStmt(` +var coreClusterMemberID = clusterDB.RegisterStmt(` SELECT core_cluster_members.id FROM core_cluster_members WHERE core_cluster_members.name = ? `) -var coreClusterMemberCreate = RegisterStmt(` +var coreClusterMemberCreate = clusterDB.RegisterStmt(` INSERT INTO core_cluster_members (name, address, certificate, schema_internal, schema_external, api_extensions, heartbeat, role) VALUES (?, ?, ?, ?, ?, ?, ?, ?) `) -var coreClusterMemberDeleteByAddress = RegisterStmt(` +var coreClusterMemberDeleteByAddress = clusterDB.RegisterStmt(` DELETE FROM core_cluster_members WHERE address = ? `) -var coreClusterMemberUpdate = RegisterStmt(` +var coreClusterMemberUpdate = clusterDB.RegisterStmt(` UPDATE core_cluster_members SET name = ?, address = ?, certificate = ?, schema_internal = ?, schema_external = ?, api_extensions = ?, heartbeat = ?, role = ? WHERE id = ? @@ -71,7 +71,7 @@ func getCoreClusterMembers(ctx context.Context, stmt *sql.Stmt, args ...any) ([] return nil } - err := db.SelectObjects(ctx, stmt, dest, args...) + err := clusterDB.SelectObjects(ctx, stmt, dest, args...) if err != nil { return nil, fmt.Errorf("Failed to fetch from \"core_cluster_members\" table: %w", err) } @@ -95,7 +95,7 @@ func getCoreClusterMembersRaw(ctx context.Context, tx *sql.Tx, sql string, args return nil } - err := db.Scan(ctx, tx, sql, dest, args...) + err := clusterDB.Scan(ctx, tx, sql, dest, args...) if err != nil { return nil, fmt.Errorf("Failed to fetch from \"core_cluster_members\" table: %w", err) } @@ -104,7 +104,6 @@ func getCoreClusterMembersRaw(ctx context.Context, tx *sql.Tx, sql string, args } // GetCoreClusterMembers returns all available core_cluster_members. -// generator: core_cluster_member GetMany func GetCoreClusterMembers(ctx context.Context, tx *sql.Tx, filters ...CoreClusterMemberFilter) ([]CoreClusterMember, error) { var err error @@ -117,7 +116,7 @@ func GetCoreClusterMembers(ctx context.Context, tx *sql.Tx, filters ...CoreClust queryParts := [2]string{} if len(filters) == 0 { - sqlStmt, err = Stmt(tx, coreClusterMemberObjects) + sqlStmt, err = clusterDB.Stmt(tx, coreClusterMemberObjects) if err != nil { return nil, fmt.Errorf("Failed to get \"coreClusterMemberObjects\" prepared statement: %w", err) } @@ -127,7 +126,7 @@ func GetCoreClusterMembers(ctx context.Context, tx *sql.Tx, filters ...CoreClust if filter.Name != nil && filter.Address == nil { args = append(args, []any{filter.Name}...) if len(filters) == 1 { - sqlStmt, err = Stmt(tx, coreClusterMemberObjectsByName) + sqlStmt, err = clusterDB.Stmt(tx, coreClusterMemberObjectsByName) if err != nil { return nil, fmt.Errorf("Failed to get \"coreClusterMemberObjectsByName\" prepared statement: %w", err) } @@ -135,7 +134,7 @@ func GetCoreClusterMembers(ctx context.Context, tx *sql.Tx, filters ...CoreClust break } - query, err := StmtString(coreClusterMemberObjectsByName) + query, err := clusterDB.StmtString(coreClusterMemberObjectsByName) if err != nil { return nil, fmt.Errorf("Failed to get \"coreClusterMemberObjects\" prepared statement: %w", err) } @@ -151,7 +150,7 @@ func GetCoreClusterMembers(ctx context.Context, tx *sql.Tx, filters ...CoreClust } else if filter.Address != nil && filter.Name == nil { args = append(args, []any{filter.Address}...) if len(filters) == 1 { - sqlStmt, err = Stmt(tx, coreClusterMemberObjectsByAddress) + sqlStmt, err = clusterDB.Stmt(tx, coreClusterMemberObjectsByAddress) if err != nil { return nil, fmt.Errorf("Failed to get \"coreClusterMemberObjectsByAddress\" prepared statement: %w", err) } @@ -159,7 +158,7 @@ func GetCoreClusterMembers(ctx context.Context, tx *sql.Tx, filters ...CoreClust break } - query, err := StmtString(coreClusterMemberObjectsByAddress) + query, err := clusterDB.StmtString(coreClusterMemberObjectsByAddress) if err != nil { return nil, fmt.Errorf("Failed to get \"coreClusterMemberObjects\" prepared statement: %w", err) } @@ -195,7 +194,6 @@ func GetCoreClusterMembers(ctx context.Context, tx *sql.Tx, filters ...CoreClust } // GetCoreClusterMember returns the core_cluster_member with the given key. -// generator: core_cluster_member GetOne func GetCoreClusterMember(ctx context.Context, tx *sql.Tx, name string) (*CoreClusterMember, error) { filter := CoreClusterMemberFilter{} filter.Name = &name @@ -216,9 +214,8 @@ func GetCoreClusterMember(ctx context.Context, tx *sql.Tx, name string) (*CoreCl } // GetCoreClusterMemberID return the ID of the core_cluster_member with the given key. -// generator: core_cluster_member ID func GetCoreClusterMemberID(ctx context.Context, tx *sql.Tx, name string) (int64, error) { - stmt, err := Stmt(tx, coreClusterMemberID) + stmt, err := clusterDB.Stmt(tx, coreClusterMemberID) if err != nil { return -1, fmt.Errorf("Failed to get \"coreClusterMemberID\" prepared statement: %w", err) } @@ -238,7 +235,6 @@ func GetCoreClusterMemberID(ctx context.Context, tx *sql.Tx, name string) (int64 } // CoreClusterMemberExists checks if a core_cluster_member with the given key exists. -// generator: core_cluster_member Exists func CoreClusterMemberExists(ctx context.Context, tx *sql.Tx, name string) (bool, error) { _, err := GetCoreClusterMemberID(ctx, tx, name) if err != nil { @@ -253,7 +249,6 @@ func CoreClusterMemberExists(ctx context.Context, tx *sql.Tx, name string) (bool } // CreateCoreClusterMember adds a new core_cluster_member to the database. -// generator: core_cluster_member Create func CreateCoreClusterMember(ctx context.Context, tx *sql.Tx, object CoreClusterMember) (int64, error) { // Check if a core_cluster_member with the same key exists. exists, err := CoreClusterMemberExists(ctx, tx, object.Name) @@ -278,7 +273,7 @@ func CreateCoreClusterMember(ctx context.Context, tx *sql.Tx, object CoreCluster args[7] = object.Role // Prepared statement to use. - stmt, err := Stmt(tx, coreClusterMemberCreate) + stmt, err := clusterDB.Stmt(tx, coreClusterMemberCreate) if err != nil { return -1, fmt.Errorf("Failed to get \"coreClusterMemberCreate\" prepared statement: %w", err) } @@ -298,9 +293,8 @@ func CreateCoreClusterMember(ctx context.Context, tx *sql.Tx, object CoreCluster } // DeleteCoreClusterMember deletes the core_cluster_member matching the given key parameters. -// generator: core_cluster_member DeleteOne-by-Address func DeleteCoreClusterMember(ctx context.Context, tx *sql.Tx, address string) error { - stmt, err := Stmt(tx, coreClusterMemberDeleteByAddress) + stmt, err := clusterDB.Stmt(tx, coreClusterMemberDeleteByAddress) if err != nil { return fmt.Errorf("Failed to get \"coreClusterMemberDeleteByAddress\" prepared statement: %w", err) } @@ -325,14 +319,13 @@ func DeleteCoreClusterMember(ctx context.Context, tx *sql.Tx, address string) er } // UpdateCoreClusterMember updates the core_cluster_member matching the given key parameters. -// generator: core_cluster_member Update func UpdateCoreClusterMember(ctx context.Context, tx *sql.Tx, name string, object CoreClusterMember) error { id, err := GetCoreClusterMemberID(ctx, tx, name) if err != nil { return err } - stmt, err := Stmt(tx, coreClusterMemberUpdate) + stmt, err := clusterDB.Stmt(tx, coreClusterMemberUpdate) if err != nil { return fmt.Errorf("Failed to get \"coreClusterMemberUpdate\" prepared statement: %w", err) } diff --git a/cluster/token_records.go b/internal/cluster/token_records.go similarity index 56% rename from cluster/token_records.go rename to internal/cluster/token_records.go index e7f8fffda..3c5e582a3 100644 --- a/cluster/token_records.go +++ b/internal/cluster/token_records.go @@ -10,28 +10,9 @@ import ( "github.com/canonical/lxd/shared" "github.com/canonical/microcluster/v3/internal/log" - internalTypes "github.com/canonical/microcluster/v3/internal/rest/types" - "github.com/canonical/microcluster/v3/rest/types" + "github.com/canonical/microcluster/v3/microcluster/types" ) -// Code generation directives. -// -//go:generate -command mapper lxd-generate db mapper -t token_records.mapper.go -//go:generate mapper reset -// -//go:generate mapper stmt -e core_token_record objects table=core_token_records -//go:generate mapper stmt -e core_token_record objects-by-Secret table=core_token_records -//go:generate mapper stmt -e core_token_record id table=core_token_records -//go:generate mapper stmt -e core_token_record create table=core_token_records -//go:generate mapper stmt -e core_token_record delete-by-Name table=core_token_records -// -//go:generate mapper method -e core_token_record ID table=core_token_records -//go:generate mapper method -e core_token_record Exists table=core_token_records -//go:generate mapper method -e core_token_record GetOne table=core_token_records -//go:generate mapper method -e core_token_record GetMany table=core_token_records -//go:generate mapper method -e core_token_record Create table=core_token_records -//go:generate mapper method -e core_token_record DeleteOne-by-Name table=core_token_records - // CoreTokenRecord is the database representation of a join token record. type CoreTokenRecord struct { ID int @@ -48,8 +29,8 @@ type CoreTokenRecordFilter struct { } // ToAPI converts the CoreTokenRecord to a full token and returns an API compatible struct. -func (t *CoreTokenRecord) ToAPI(clusterCert *x509.Certificate, joinAddresses []types.AddrPort) (*internalTypes.TokenRecord, error) { - token := internalTypes.Token{ +func (t *CoreTokenRecord) ToAPI(clusterCert *x509.Certificate, joinAddresses []types.AddrPort) (*types.TokenRecord, error) { + token := types.Token{ Secret: t.Secret, Fingerprint: shared.CertFingerprint(clusterCert), JoinAddresses: joinAddresses, @@ -60,7 +41,7 @@ func (t *CoreTokenRecord) ToAPI(clusterCert *x509.Certificate, joinAddresses []t return nil, err } - return &internalTypes.TokenRecord{ + return &types.TokenRecord{ Token: tokenString, Name: t.Name, ExpiresAt: t.ExpiryDate.Time, diff --git a/cluster/token_records.mapper.go b/internal/cluster/token_records.mapper.go similarity index 88% rename from cluster/token_records.mapper.go rename to internal/cluster/token_records.mapper.go index eed261015..4ca623c8a 100644 --- a/cluster/token_records.mapper.go +++ b/internal/cluster/token_records.mapper.go @@ -10,42 +10,41 @@ import ( "github.com/canonical/lxd/shared/api" - "github.com/canonical/microcluster/v3/cluster/db" + clusterDB "github.com/canonical/microcluster/v3/microcluster/db" ) var _ = api.ServerEnvironment{} -var coreTokenRecordObjects = RegisterStmt(` +var coreTokenRecordObjects = clusterDB.RegisterStmt(` SELECT core_token_records.id, core_token_records.secret, core_token_records.name, core_token_records.expiry_date FROM core_token_records ORDER BY core_token_records.secret `) -var coreTokenRecordObjectsBySecret = RegisterStmt(` +var coreTokenRecordObjectsBySecret = clusterDB.RegisterStmt(` SELECT core_token_records.id, core_token_records.secret, core_token_records.name, core_token_records.expiry_date FROM core_token_records WHERE ( core_token_records.secret = ? ) ORDER BY core_token_records.secret `) -var coreTokenRecordID = RegisterStmt(` +var coreTokenRecordID = clusterDB.RegisterStmt(` SELECT core_token_records.id FROM core_token_records WHERE core_token_records.secret = ? `) -var coreTokenRecordCreate = RegisterStmt(` +var coreTokenRecordCreate = clusterDB.RegisterStmt(` INSERT INTO core_token_records (secret, name, expiry_date) VALUES (?, ?, ?) `) -var coreTokenRecordDeleteByName = RegisterStmt(` +var coreTokenRecordDeleteByName = clusterDB.RegisterStmt(` DELETE FROM core_token_records WHERE name = ? `) // GetCoreTokenRecordID return the ID of the core_token_record with the given key. -// generator: core_token_record ID func GetCoreTokenRecordID(ctx context.Context, tx *sql.Tx, secret string) (int64, error) { - stmt, err := Stmt(tx, coreTokenRecordID) + stmt, err := clusterDB.Stmt(tx, coreTokenRecordID) if err != nil { return -1, fmt.Errorf("Failed to get \"coreTokenRecordID\" prepared statement: %w", err) } @@ -65,7 +64,6 @@ func GetCoreTokenRecordID(ctx context.Context, tx *sql.Tx, secret string) (int64 } // CoreTokenRecordExists checks if a core_token_record with the given key exists. -// generator: core_token_record Exists func CoreTokenRecordExists(ctx context.Context, tx *sql.Tx, secret string) (bool, error) { _, err := GetCoreTokenRecordID(ctx, tx, secret) if err != nil { @@ -80,7 +78,6 @@ func CoreTokenRecordExists(ctx context.Context, tx *sql.Tx, secret string) (bool } // GetCoreTokenRecord returns the core_token_record with the given key. -// generator: core_token_record GetOne func GetCoreTokenRecord(ctx context.Context, tx *sql.Tx, secret string) (*CoreTokenRecord, error) { filter := CoreTokenRecordFilter{} filter.Secret = &secret @@ -116,7 +113,7 @@ func getCoreTokenRecords(ctx context.Context, stmt *sql.Stmt, args ...any) ([]Co return nil } - err := db.SelectObjects(ctx, stmt, dest, args...) + err := clusterDB.SelectObjects(ctx, stmt, dest, args...) if err != nil { return nil, fmt.Errorf("Failed to fetch from \"core_token_records\" table: %w", err) } @@ -140,7 +137,7 @@ func getCoreTokenRecordsRaw(ctx context.Context, tx *sql.Tx, sql string, args .. return nil } - err := db.Scan(ctx, tx, sql, dest, args...) + err := clusterDB.Scan(ctx, tx, sql, dest, args...) if err != nil { return nil, fmt.Errorf("Failed to fetch from \"core_token_records\" table: %w", err) } @@ -149,7 +146,6 @@ func getCoreTokenRecordsRaw(ctx context.Context, tx *sql.Tx, sql string, args .. } // GetCoreTokenRecords returns all available core_token_records. -// generator: core_token_record GetMany func GetCoreTokenRecords(ctx context.Context, tx *sql.Tx, filters ...CoreTokenRecordFilter) ([]CoreTokenRecord, error) { var err error @@ -162,7 +158,7 @@ func GetCoreTokenRecords(ctx context.Context, tx *sql.Tx, filters ...CoreTokenRe queryParts := [2]string{} if len(filters) == 0 { - sqlStmt, err = Stmt(tx, coreTokenRecordObjects) + sqlStmt, err = clusterDB.Stmt(tx, coreTokenRecordObjects) if err != nil { return nil, fmt.Errorf("Failed to get \"coreTokenRecordObjects\" prepared statement: %w", err) } @@ -172,7 +168,7 @@ func GetCoreTokenRecords(ctx context.Context, tx *sql.Tx, filters ...CoreTokenRe if filter.Secret != nil && filter.ID == nil && filter.Name == nil { args = append(args, []any{filter.Secret}...) if len(filters) == 1 { - sqlStmt, err = Stmt(tx, coreTokenRecordObjectsBySecret) + sqlStmt, err = clusterDB.Stmt(tx, coreTokenRecordObjectsBySecret) if err != nil { return nil, fmt.Errorf("Failed to get \"coreTokenRecordObjectsBySecret\" prepared statement: %w", err) } @@ -180,7 +176,7 @@ func GetCoreTokenRecords(ctx context.Context, tx *sql.Tx, filters ...CoreTokenRe break } - query, err := StmtString(coreTokenRecordObjectsBySecret) + query, err := clusterDB.StmtString(coreTokenRecordObjectsBySecret) if err != nil { return nil, fmt.Errorf("Failed to get \"coreTokenRecordObjects\" prepared statement: %w", err) } @@ -216,7 +212,6 @@ func GetCoreTokenRecords(ctx context.Context, tx *sql.Tx, filters ...CoreTokenRe } // CreateCoreTokenRecord adds a new core_token_record to the database. -// generator: core_token_record Create func CreateCoreTokenRecord(ctx context.Context, tx *sql.Tx, object CoreTokenRecord) (int64, error) { // Check if a core_token_record with the same key exists. exists, err := CoreTokenRecordExists(ctx, tx, object.Secret) @@ -236,7 +231,7 @@ func CreateCoreTokenRecord(ctx context.Context, tx *sql.Tx, object CoreTokenReco args[2] = object.ExpiryDate // Prepared statement to use. - stmt, err := Stmt(tx, coreTokenRecordCreate) + stmt, err := clusterDB.Stmt(tx, coreTokenRecordCreate) if err != nil { return -1, fmt.Errorf("Failed to get \"coreTokenRecordCreate\" prepared statement: %w", err) } @@ -256,9 +251,8 @@ func CreateCoreTokenRecord(ctx context.Context, tx *sql.Tx, object CoreTokenReco } // DeleteCoreTokenRecord deletes the core_token_record matching the given key parameters. -// generator: core_token_record DeleteOne-by-Name func DeleteCoreTokenRecord(ctx context.Context, tx *sql.Tx, name string) error { - stmt, err := Stmt(tx, coreTokenRecordDeleteByName) + stmt, err := clusterDB.Stmt(tx, coreTokenRecordDeleteByName) if err != nil { return fmt.Errorf("Failed to get \"coreTokenRecordDeleteByName\" prepared statement: %w", err) } diff --git a/internal/config/daemon.go b/internal/config/daemon.go index 1e1285c81..615c93593 100644 --- a/internal/config/daemon.go +++ b/internal/config/daemon.go @@ -7,7 +7,7 @@ import ( "gopkg.in/yaml.v3" - "github.com/canonical/microcluster/v3/rest/types" + "github.com/canonical/microcluster/v3/microcluster/types" ) // DaemonConfig wraps the daemon's config with get, set and lock capabilities. diff --git a/internal/daemon/daemon.go b/internal/daemon/daemon.go index 7e79149fc..15fa61f31 100644 --- a/internal/daemon/daemon.go +++ b/internal/daemon/daemon.go @@ -23,8 +23,7 @@ import ( "github.com/mattn/go-sqlite3" "github.com/canonical/microcluster/v3/client" - "github.com/canonical/microcluster/v3/cluster" - clusterDB "github.com/canonical/microcluster/v3/cluster/db" + "github.com/canonical/microcluster/v3/internal/cluster" internalConfig "github.com/canonical/microcluster/v3/internal/config" "github.com/canonical/microcluster/v3/internal/db" "github.com/canonical/microcluster/v3/internal/endpoints" @@ -34,14 +33,14 @@ import ( internalREST "github.com/canonical/microcluster/v3/internal/rest" internalClient "github.com/canonical/microcluster/v3/internal/rest/client" "github.com/canonical/microcluster/v3/internal/rest/resources" - internalTypes "github.com/canonical/microcluster/v3/internal/rest/types" internalState "github.com/canonical/microcluster/v3/internal/state" "github.com/canonical/microcluster/v3/internal/sys" "github.com/canonical/microcluster/v3/internal/trust" "github.com/canonical/microcluster/v3/internal/utils" - "github.com/canonical/microcluster/v3/rest" - "github.com/canonical/microcluster/v3/rest/response" - "github.com/canonical/microcluster/v3/rest/types" + clusterDB "github.com/canonical/microcluster/v3/microcluster/db" + "github.com/canonical/microcluster/v3/microcluster/rest" + "github.com/canonical/microcluster/v3/microcluster/rest/response" + "github.com/canonical/microcluster/v3/microcluster/types" "github.com/canonical/microcluster/v3/state" ) @@ -724,7 +723,7 @@ func (d *Daemon) StartAPI(ctx context.Context, bootstrap bool, initConfig map[st } // Run the OnNewMember hook, and skip errors on any nodes that are still in the process of joining. - err = internalClient.RunNewMemberHook(ctx, c.Client.UseTarget(remote.Name), internalTypes.HookNewMemberOptions{NewMember: localMemberInfo}) + err = internalClient.RunNewMemberHook(ctx, c.Client.UseTarget(remote.Name), types.HookNewMemberOptions{NewMember: localMemberInfo}) if err != nil && !api.StatusErrorCheck(err, http.StatusServiceUnavailable) { // log error but continue with other nodes d.log().Warn("Failed running OnNewMember hook on node", slog.String("node", c.URL().URL.Host), slog.String("error", err.Error())) @@ -956,7 +955,7 @@ func (d *Daemon) addExtensionServers(preInit bool, fallbackCert *shared.CertInfo func (d *Daemon) sendUpgradeNotification(ctx context.Context, c *client.Client) error { path := c.URL() - parts := strings.Split(string(internalTypes.InternalEndpoint), "/") + parts := strings.Split(string(types.InternalEndpoint), "/") parts = append(parts, "database") path = *path.Path(parts...) upgradeRequest, err := http.NewRequest("PATCH", path.String(), nil) diff --git a/internal/daemon/daemon_test.go b/internal/daemon/daemon_test.go index a6dda3f0c..85c1b1570 100644 --- a/internal/daemon/daemon_test.go +++ b/internal/daemon/daemon_test.go @@ -17,8 +17,8 @@ import ( "github.com/canonical/microcluster/v3/internal/rest/client" "github.com/canonical/microcluster/v3/internal/sys" "github.com/canonical/microcluster/v3/internal/trust" - "github.com/canonical/microcluster/v3/rest" - "github.com/canonical/microcluster/v3/rest/types" + "github.com/canonical/microcluster/v3/microcluster/rest" + "github.com/canonical/microcluster/v3/microcluster/types" ) type daemonsSuite struct { diff --git a/internal/daemon/logfilter.go b/internal/daemon/logfilter.go index e7d0f12b8..d1eafbd5e 100644 --- a/internal/daemon/logfilter.go +++ b/internal/daemon/logfilter.go @@ -6,7 +6,7 @@ import ( "regexp" "strings" - "github.com/canonical/microcluster/v3/rest/types" + "github.com/canonical/microcluster/v3/microcluster/types" ) // logFilter represents a filter for log messages caused by known addresses. diff --git a/internal/db/db.go b/internal/db/db.go index 93d2ae244..faf3ea6e1 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -16,12 +16,12 @@ import ( "github.com/canonical/lxd/shared/api" "github.com/canonical/lxd/shared/revert" - "github.com/canonical/microcluster/v3/cluster" "github.com/canonical/microcluster/v3/internal/db/query" "github.com/canonical/microcluster/v3/internal/db/update" "github.com/canonical/microcluster/v3/internal/extensions" "github.com/canonical/microcluster/v3/internal/sys" - "github.com/canonical/microcluster/v3/rest/types" + clusterDB "github.com/canonical/microcluster/v3/microcluster/db" + "github.com/canonical/microcluster/v3/microcluster/types" ) // Open opens the dqlite database and loads the schema. @@ -71,7 +71,7 @@ func (db *DqliteDB) Open(ext extensions.Extensions, bootstrap bool) error { db.log().Info("Preparing statements") - err = cluster.PrepareStmts(db.db, false) + err = clusterDB.PrepareStmts(db.db, false) if err != nil { return err } diff --git a/internal/db/db_test.go b/internal/db/db_test.go index a0b74b148..b12a754d6 100644 --- a/internal/db/db_test.go +++ b/internal/db/db_test.go @@ -12,12 +12,12 @@ import ( "github.com/canonical/lxd/shared/api" "github.com/stretchr/testify/suite" - "github.com/canonical/microcluster/v3/cluster" - clusterDB "github.com/canonical/microcluster/v3/cluster/db" + "github.com/canonical/microcluster/v3/internal/cluster" "github.com/canonical/microcluster/v3/internal/db/update" "github.com/canonical/microcluster/v3/internal/extensions" "github.com/canonical/microcluster/v3/internal/log" "github.com/canonical/microcluster/v3/internal/sys" + clusterDB "github.com/canonical/microcluster/v3/microcluster/db" ) type dbSuite struct { @@ -673,7 +673,7 @@ func NewTestDB(extensionsExternal []clusterDB.Update) (*DqliteDB, error) { return nil, err } - err = cluster.PrepareStmts(db.db, false) + err = clusterDB.PrepareStmts(db.db, false) if err != nil { return nil, err } diff --git a/internal/db/dqlite.go b/internal/db/dqlite.go index f5d44c548..44c8c832b 100644 --- a/internal/db/dqlite.go +++ b/internal/db/dqlite.go @@ -24,15 +24,14 @@ import ( "github.com/canonical/lxd/shared/revert" "github.com/canonical/lxd/shared/tcp" - "github.com/canonical/microcluster/v3/cluster" - clusterDB "github.com/canonical/microcluster/v3/cluster/db" + "github.com/canonical/microcluster/v3/internal/cluster" "github.com/canonical/microcluster/v3/internal/db/update" "github.com/canonical/microcluster/v3/internal/extensions" "github.com/canonical/microcluster/v3/internal/log" internalClient "github.com/canonical/microcluster/v3/internal/rest/client" - internalTypes "github.com/canonical/microcluster/v3/internal/rest/types" "github.com/canonical/microcluster/v3/internal/sys" - "github.com/canonical/microcluster/v3/rest/types" + clusterDB "github.com/canonical/microcluster/v3/microcluster/db" + "github.com/canonical/microcluster/v3/microcluster/types" ) // DqliteDB holds all information internal to the dqlite database. @@ -334,13 +333,13 @@ func (db *DqliteDB) GetHeartbeatInterval() time.Duration { } // SendHeartbeat initiates a new heartbeat sequence if this is a leader node. -func (db *DqliteDB) SendHeartbeat(ctx context.Context, c *internalClient.Client, hbInfo internalTypes.HeartbeatInfo) error { +func (db *DqliteDB) SendHeartbeat(ctx context.Context, c *internalClient.Client, hbInfo types.HeartbeatInfo) error { // set the heartbeat timeout to twice the heartbeat interval. heartbeatTimeout := db.heartbeatInterval * 2 queryCtx, cancel := context.WithTimeout(ctx, heartbeatTimeout) defer cancel() - return c.QueryStruct(queryCtx, "POST", internalTypes.InternalEndpoint, &api.NewURL().Path("heartbeat").URL, hbInfo, nil) + return c.QueryStruct(queryCtx, "POST", types.InternalEndpoint, &api.NewURL().Path("heartbeat").URL, hbInfo, nil) } func (db *DqliteDB) heartbeat(leaderInfo dqliteClient.NodeInfo, servers []dqliteClient.NodeInfo) error { @@ -365,7 +364,7 @@ func (db *DqliteDB) heartbeat(leaderInfo dqliteClient.NodeInfo, servers []dqlite } // Initiate a heartbeat from this node. - hbInfo := internalTypes.HeartbeatInfo{ + hbInfo := types.HeartbeatInfo{ BeginRound: true, LeaderAddress: leaderInfo.Address, DqliteRoles: make(map[string]string, len(servers)), @@ -414,7 +413,7 @@ func dqliteNetworkDial(ctx context.Context, addr string, db *DqliteDB) (net.Conn request.URL = &url.URL{ Scheme: "https", Host: addrPort.String(), - Path: fmt.Sprintf("/%s/%s", internalTypes.InternalEndpoint, "database"), + Path: fmt.Sprintf("/%s/%s", types.InternalEndpoint, "database"), } request.Header.Set("Upgrade", "dqlite") diff --git a/internal/db/interface.go b/internal/db/interface.go index c3fdfaec2..6f7f39091 100644 --- a/internal/db/interface.go +++ b/internal/db/interface.go @@ -7,7 +7,7 @@ import ( dqliteClient "github.com/canonical/go-dqlite/v3/client" "github.com/canonical/microcluster/v3/internal/extensions" - "github.com/canonical/microcluster/v3/rest/types" + "github.com/canonical/microcluster/v3/microcluster/types" ) // DB exposes the internal database for use with external projects. diff --git a/internal/db/query/transaction_test.go b/internal/db/query/transaction_test.go index f4d405de7..5ad12f826 100644 --- a/internal/db/query/transaction_test.go +++ b/internal/db/query/transaction_test.go @@ -11,9 +11,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - dbClient "github.com/canonical/microcluster/v3/cluster/db" "github.com/canonical/microcluster/v3/internal/db/query" "github.com/canonical/microcluster/v3/internal/log" + clusterDB "github.com/canonical/microcluster/v3/microcluster/db" ) // Any error happening when beginning the transaction will be propagated. @@ -47,7 +47,7 @@ func TestTransaction_FunctionError(t *testing.T) { tx, err := db.Begin() assert.NoError(t, err) - tables, err := dbClient.SelectStrings(context.Background(), tx, "SELECT name FROM sqlite_master WHERE type = 'table'") + tables, err := clusterDB.SelectStrings(context.Background(), tx, "SELECT name FROM sqlite_master WHERE type = 'table'") assert.NoError(t, err) assert.NotContains(t, tables, "test") _ = tx.Rollback() diff --git a/internal/db/update/cluster.go b/internal/db/update/cluster.go index 2bfd5eed6..80dba5405 100644 --- a/internal/db/update/cluster.go +++ b/internal/db/update/cluster.go @@ -6,9 +6,9 @@ import ( "fmt" "log/slog" - "github.com/canonical/microcluster/v3/cluster/db" "github.com/canonical/microcluster/v3/internal/extensions" "github.com/canonical/microcluster/v3/internal/log" + clusterDB "github.com/canonical/microcluster/v3/microcluster/db" ) // PrepareUpdateV1 creates the temporary table `internal_cluster_members_new` if we have not yet run `updateFromV1`. @@ -134,7 +134,7 @@ func GetClusterMemberSchemaVersions(ctx context.Context, tx *sql.Tx) (internalSc return nil } - err = db.Scan(ctx, tx, sql, dest) + err = clusterDB.Scan(ctx, tx, sql, dest) if err != nil { return nil, nil, err } @@ -240,7 +240,7 @@ func GetClusterMemberAPIExtensions(ctx context.Context, tx *sql.Tx) ([]extension // Since we need to check this table to perform the update that renames it, we can use this function to dynamically determine its name. func getClusterTableName(ctx context.Context, tx *sql.Tx) (string, error) { stmt := "SELECT name FROM sqlite_master WHERE name = 'internal_cluster_members' OR name = 'core_cluster_members'" - tables, err := db.SelectStrings(ctx, tx, stmt) + tables, err := clusterDB.SelectStrings(ctx, tx, stmt) if err != nil { return "", err } diff --git a/internal/db/update/schema.go b/internal/db/update/schema.go index c0d82e755..b2a149d4f 100644 --- a/internal/db/update/schema.go +++ b/internal/db/update/schema.go @@ -9,10 +9,10 @@ import ( "github.com/canonical/lxd/shared" - clusterDB "github.com/canonical/microcluster/v3/cluster/db" "github.com/canonical/microcluster/v3/internal/db/query" "github.com/canonical/microcluster/v3/internal/db/schema" "github.com/canonical/microcluster/v3/internal/extensions" + clusterDB "github.com/canonical/microcluster/v3/microcluster/db" ) // updateType represents whether the update is an internal or external schema update. diff --git a/internal/db/update/update.go b/internal/db/update/update.go index 6886a6271..01b79c8b1 100644 --- a/internal/db/update/update.go +++ b/internal/db/update/update.go @@ -5,8 +5,8 @@ import ( "database/sql" "fmt" - clusterDB "github.com/canonical/microcluster/v3/cluster/db" "github.com/canonical/microcluster/v3/internal/extensions" + clusterDB "github.com/canonical/microcluster/v3/microcluster/db" ) // CreateSchema is the default schema applied when bootstrapping the database. diff --git a/internal/db/update/update_test.go b/internal/db/update/update_test.go index 225bb37aa..9ea600fa9 100644 --- a/internal/db/update/update_test.go +++ b/internal/db/update/update_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/suite" - clusterDB "github.com/canonical/microcluster/v3/cluster/db" + clusterDB "github.com/canonical/microcluster/v3/microcluster/db" ) type updateSuite struct { diff --git a/internal/recover/recover.go b/internal/recover/recover.go index e6f5ac3d8..3e7ba9375 100644 --- a/internal/recover/recover.go +++ b/internal/recover/recover.go @@ -22,18 +22,16 @@ import ( "gopkg.in/yaml.v3" "github.com/canonical/microcluster/v3/client" - "github.com/canonical/microcluster/v3/cluster" "github.com/canonical/microcluster/v3/internal/config" "github.com/canonical/microcluster/v3/internal/log" - internalTypes "github.com/canonical/microcluster/v3/internal/rest/types" "github.com/canonical/microcluster/v3/internal/sys" "github.com/canonical/microcluster/v3/internal/trust" - "github.com/canonical/microcluster/v3/rest/types" + "github.com/canonical/microcluster/v3/microcluster/types" ) // GetDqliteClusterMembers parses the trust store and // path.Join(filesystem.DatabaseDir, "cluster.yaml"). -func GetDqliteClusterMembers(filesystem *sys.OS) ([]cluster.DqliteMember, error) { +func GetDqliteClusterMembers(filesystem *sys.OS) ([]types.DqliteMember, error) { storePath := path.Join(filesystem.DatabaseDir, "cluster.yaml") var nodeInfo []dqlite.NodeInfo @@ -49,11 +47,11 @@ func GetDqliteClusterMembers(filesystem *sys.OS) ([]cluster.DqliteMember, error) remotesByName := remotes.RemotesByName() - var members []cluster.DqliteMember + var members []types.DqliteMember for _, remote := range remotesByName { for _, info := range nodeInfo { if remote.Address.String() == info.Address { - members = append(members, cluster.DqliteMember{ + members = append(members, types.DqliteMember{ DqliteID: info.ID, Address: info.Address, Role: info.Role.String(), @@ -70,7 +68,7 @@ func GetDqliteClusterMembers(filesystem *sys.OS) ([]cluster.DqliteMember, error) // files, modifies the daemon and trust store, and writes a recovery tarball. // It does not check members to ensure that the new configuration is valid; use // ValidateMemberChanges to ensure that the inputs to this function are correct. -func RecoverFromQuorumLoss(ctx context.Context, filesystem *sys.OS, members []cluster.DqliteMember) (string, error) { +func RecoverFromQuorumLoss(ctx context.Context, filesystem *sys.OS, members []types.DqliteMember) (string, error) { // Set up our new cluster configuration nodeInfo := make([]dqlite.NodeInfo, 0, len(members)) for _, member := range members { @@ -122,8 +120,8 @@ func RecoverFromQuorumLoss(ctx context.Context, filesystem *sys.OS, members []cl cancelCtx, cancel := context.WithTimeout(context.Background(), time.Second*10) err = cluster.Query(cancelCtx, true, func(ctx context.Context, client *client.Client) error { - var rslt internalTypes.Server - err := client.Query(ctx, "GET", internalTypes.PublicEndpoint, &api.NewURL().URL, nil, &rslt) + var rslt types.Server + err := client.Query(ctx, "GET", types.PublicEndpoint, &api.NewURL().URL, nil, &rslt) if err == nil { return fmt.Errorf("Contacted cluster member at %q; please shut down all cluster members", rslt.Name) } @@ -223,7 +221,7 @@ func writeYaml(path string, v any) error { return nil } -func writeDqliteClusterYaml(path string, members []cluster.DqliteMember) error { +func writeDqliteClusterYaml(path string, members []types.DqliteMember) error { nodeInfo := make([]dqlite.NodeInfo, len(members)) for i, member := range members { infoPtr, err := member.NodeInfo() @@ -267,7 +265,7 @@ func readTrustStore(dir string) (*trust.Remotes, error) { } // Update the trust store with the new member addresses. -func updateTrustStore(dir string, members []cluster.DqliteMember) error { +func updateTrustStore(dir string, members []types.DqliteMember) error { remotes, err := readTrustStore(dir) if err != nil { return err @@ -306,7 +304,7 @@ func updateTrustStore(dir string, members []cluster.DqliteMember) error { // - There is at least one voter in newMembers. // - All the newMembers addresses can be parsed to a netip.AddrPort. // - There are no duplicate addresses. -func ValidateMemberChanges(oldMembers []cluster.DqliteMember, newMembers []cluster.DqliteMember) error { +func ValidateMemberChanges(oldMembers []types.DqliteMember, newMembers []types.DqliteMember) error { if len(newMembers) != len(oldMembers) { return fmt.Errorf("members cannot be added or removed") } @@ -352,7 +350,7 @@ func ValidateMemberChanges(oldMembers []cluster.DqliteMember, newMembers []clust return nil } -func writeGlobalMembersPatch(filesystem *sys.OS, members []cluster.DqliteMember) error { +func writeGlobalMembersPatch(filesystem *sys.OS, members []types.DqliteMember) error { sql := "" for _, member := range members { sql += fmt.Sprintf("UPDATE core_cluster_members SET address = %q WHERE name = %q;\n", member.Address, member.Name) @@ -386,7 +384,7 @@ func writeGlobalMembersPatch(filesystem *sys.OS, members []cluster.DqliteMember) // go-dqlite's info.yaml is excluded from the tarball. // The new cluster configuration is included as `recovery.yaml`. // This function returns the path to the tarball. -func createRecoveryTarball(ctx context.Context, filesystem *sys.OS, members []cluster.DqliteMember) (string, error) { +func createRecoveryTarball(ctx context.Context, filesystem *sys.OS, members []types.DqliteMember) (string, error) { tarballPath := path.Join(filesystem.StateDir, "recovery_db.tar.gz") recoveryYamlPath := path.Join(filesystem.DatabaseDir, "recovery.yaml") @@ -441,7 +439,7 @@ func MaybeUnpackRecoveryTarball(ctx context.Context, filesystem *sys.OS) error { return err } - var incomingMembers []cluster.DqliteMember + var incomingMembers []types.DqliteMember err = readYaml(recoveryYamlPath, &incomingMembers) if err != nil { return nil diff --git a/internal/rest/access/authentication.go b/internal/rest/access/authentication.go index 2f5333bb9..b522fabe6 100644 --- a/internal/rest/access/authentication.go +++ b/internal/rest/access/authentication.go @@ -2,9 +2,21 @@ package access import ( "context" + "crypto/subtle" + "crypto/x509" + "fmt" + "log/slog" "net/http" + "time" + "github.com/canonical/lxd/shared/api" + + "github.com/canonical/microcluster/v3/internal/endpoints" + "github.com/canonical/microcluster/v3/internal/log" "github.com/canonical/microcluster/v3/internal/rest/client" + "github.com/canonical/microcluster/v3/internal/state" + "github.com/canonical/microcluster/v3/microcluster/rest/response" + "github.com/canonical/microcluster/v3/microcluster/types" ) // TrustedRequest holds data pertaining to what level of trust we have for the request. @@ -12,6 +24,130 @@ type TrustedRequest struct { Trusted bool } +// ErrInvalidHost is used to indicate that a request host is invalid. +type ErrInvalidHost struct { + error +} + +// Unwrap implements xerrors.Unwrap for ErrInvalidHost. +func (e ErrInvalidHost) Unwrap() error { + return e.error +} + +// AllowAuthenticated checks if the request is trusted by extracting access.TrustedRequest from the request context. +// This handler is used as an access handler by default if AllowUntrusted is false on a rest.EndpointAction. +func AllowAuthenticated(state state.State, r *http.Request) (bool, response.Response) { + trusted := r.Context().Value(client.CtxAccess) + if trusted == nil { + return false, response.Forbidden(nil) + } + + trustedReq, ok := trusted.(TrustedRequest) + if !ok { + return false, response.Forbidden(nil) + } + + if !trustedReq.Trusted { + return false, response.Forbidden(nil) + } + + return true, nil +} + +// certificateInDate returns an error if the current time is before the certificates "not before", or after the +// certificates "not after". +func certificateInDate(cert x509.Certificate) error { + now := time.Now() + if now.Before(cert.NotBefore) { + return api.StatusErrorf(http.StatusUnauthorized, "Certificate is not yet valid") + } + + if now.After(cert.NotAfter) { + return api.StatusErrorf(http.StatusUnauthorized, "Certificate has expired") + } + + return nil +} + +// checkMutualTLS checks whether the given certificate is valid and is present in the given trustedCerts map. +// Returns true if the certificate is trusted, and the fingerprint of the certificate. +func checkMutualTLS(ctx context.Context, cert x509.Certificate, trustedCerts map[string]x509.Certificate) (bool, string) { + err := certificateInDate(cert) + if err != nil { + return false, "" + } + + logger, err := log.LoggerFromContext(ctx) + if err != nil { + // We failed to get the logger so we can't log the error. + return false, "" + } + + // Check whether client certificate is in the map of trusted certs. + for fingerprint, v := range trustedCerts { + if subtle.ConstantTimeCompare(cert.Raw, v.Raw) == 1 { + logger.Debug("Matched trusted cert", slog.String("fingerprint", fingerprint), slog.String("subject", v.Subject.String())) + return true, fingerprint + } + } + + return false, "" +} + +// Authenticate ensures the request certificates are trusted against the given set of trusted certificates. +// - Requests over the unix socket are always allowed. +// - HTTP requests require the TLS Peer certificate to match an entry in the supplied map of certificates. +func Authenticate(s state.State, r *http.Request, hostAddress string, trustedCerts map[string]x509.Certificate) (bool, error) { + if r.RemoteAddr == "@" { + return true, nil + } + + intState, err := state.ToInternal(s) + if err != nil { + return false, err + } + + logger, err := log.LoggerFromContext(r.Context()) + if err != nil { + return false, err + } + + // Check if it's the core API listener and if it is using the server.crt. + // This indicates that the daemon is in a pre-init state and is listening on the PreInitListenAddress. + endpoint := intState.Endpoints.Get(endpoints.EndpointsCore) + network, ok := endpoint.(*endpoints.Network) + if ok { + if s.ServerCert().Fingerprint() == network.TLS().Fingerprint() { + logger.Info("Allowing unauthenticated request to un-initialized system") + return true, nil + } + } + + // Ensure the given host address is valid. + hostAddrPort, err := types.ParseAddrPort(hostAddress) + if err != nil { + return false, fmt.Errorf("Invalid host address %q", hostAddress) + } + + switch r.Host { + case hostAddrPort.WithZone("").String(): + if r.TLS != nil { + for _, cert := range r.TLS.PeerCertificates { + trusted, fingerprint := checkMutualTLS(r.Context(), *cert, trustedCerts) + if trusted { + logger.Debug("Authenticated request", slog.String("origin", r.RemoteAddr), slog.String("destination", r.URL.String()), slog.String("fingerprint", fingerprint)) + return trusted, nil + } + } + } + + default: + return false, ErrInvalidHost{error: fmt.Errorf("Invalid request address %q", r.Host)} + } + + return false, nil +} + // SetRequestAuthentication sets the trusted status for the request. A trusted request will be treated as having come from a trusted system. func SetRequestAuthentication(r *http.Request, trusted bool) *http.Request { r = r.WithContext(context.WithValue(r.Context(), any(client.CtxAccess), TrustedRequest{Trusted: trusted})) diff --git a/internal/rest/client/client.go b/internal/rest/client/client.go index 5bb3ae665..7d12b4e88 100644 --- a/internal/rest/client/client.go +++ b/internal/rest/client/client.go @@ -21,8 +21,8 @@ import ( "github.com/canonical/lxd/shared/tcp" "github.com/gorilla/websocket" - "github.com/canonical/microcluster/v3/rest/response" - "github.com/canonical/microcluster/v3/rest/types" + "github.com/canonical/microcluster/v3/microcluster/rest/response" + "github.com/canonical/microcluster/v3/microcluster/types" ) // Client is a rest client for the daemon. diff --git a/internal/rest/client/cluster.go b/internal/rest/client/cluster.go index d8587a424..976c980d6 100644 --- a/internal/rest/client/cluster.go +++ b/internal/rest/client/cluster.go @@ -6,8 +6,7 @@ import ( "github.com/canonical/lxd/shared/api" - internalTypes "github.com/canonical/microcluster/v3/internal/rest/types" - "github.com/canonical/microcluster/v3/rest/types" + "github.com/canonical/microcluster/v3/microcluster/types" ) // withTimeoutIfUnset returns a context with a 30s timeout only if the parent context has no deadline set. @@ -21,12 +20,12 @@ func withTimeoutIfUnset(ctx context.Context) (context.Context, context.CancelFun } // AddClusterMember records a new cluster member in the trust store of each current cluster member. -func AddClusterMember(ctx context.Context, c *Client, args types.ClusterMember) (*internalTypes.TokenResponse, error) { +func AddClusterMember(ctx context.Context, c *Client, args types.ClusterMember) (*types.TokenResponse, error) { queryCtx, cancel := withTimeoutIfUnset(ctx) defer cancel() - tokenResponse := internalTypes.TokenResponse{} - err := c.QueryStruct(queryCtx, "POST", internalTypes.InternalEndpoint, &api.NewURL().Path("cluster").URL, args, &tokenResponse) + tokenResponse := types.TokenResponse{} + err := c.QueryStruct(queryCtx, "POST", types.InternalEndpoint, &api.NewURL().Path("cluster").URL, args, &tokenResponse) if err != nil { return nil, err } @@ -44,7 +43,7 @@ func ResetClusterMember(ctx context.Context, c *Client, name string, force bool) endpoint = endpoint.WithQuery("force", "1") } - return c.QueryStruct(queryCtx, "PUT", internalTypes.InternalEndpoint, &endpoint.URL, nil, nil) + return c.QueryStruct(queryCtx, "PUT", types.InternalEndpoint, &endpoint.URL, nil, nil) } // GetClusterMembers returns the database record of cluster members. @@ -53,7 +52,7 @@ func (c *Client) GetClusterMembers(ctx context.Context) ([]types.ClusterMember, defer cancel() clusterMembers := []types.ClusterMember{} - err := c.QueryStruct(queryCtx, "GET", internalTypes.PublicEndpoint, &api.NewURL().Path("cluster").URL, nil, &clusterMembers) + err := c.QueryStruct(queryCtx, "GET", types.PublicEndpoint, &api.NewURL().Path("cluster").URL, nil, &clusterMembers) return clusterMembers, err } @@ -68,7 +67,7 @@ func (c *Client) DeleteClusterMember(ctx context.Context, name string, force boo endpoint = endpoint.WithQuery("force", "1") } - return c.QueryStruct(queryCtx, "DELETE", internalTypes.PublicEndpoint, &endpoint.URL, nil, nil) + return c.QueryStruct(queryCtx, "DELETE", types.PublicEndpoint, &endpoint.URL, nil, nil) } // UpdateCertificate sets a new keypair and CA. @@ -77,5 +76,5 @@ func (c *Client) UpdateCertificate(ctx context.Context, name types.CertificateNa defer cancel() endpoint := api.NewURL().Path("cluster", "certificates", string(name)) - return c.QueryStruct(queryCtx, "PUT", internalTypes.PublicEndpoint, &endpoint.URL, args, nil) + return c.QueryStruct(queryCtx, "PUT", types.PublicEndpoint, &endpoint.URL, args, nil) } diff --git a/internal/rest/client/control.go b/internal/rest/client/control.go index 4c59e2e20..5bed31ffa 100644 --- a/internal/rest/client/control.go +++ b/internal/rest/client/control.go @@ -3,7 +3,7 @@ package client import ( "context" - "github.com/canonical/microcluster/v3/internal/rest/types" + "github.com/canonical/microcluster/v3/microcluster/types" ) // ControlDaemon posts control data to the daemon. diff --git a/internal/rest/client/daemon.go b/internal/rest/client/daemon.go index 3aae80a01..749894620 100644 --- a/internal/rest/client/daemon.go +++ b/internal/rest/client/daemon.go @@ -6,12 +6,11 @@ import ( "github.com/canonical/lxd/shared/api" - "github.com/canonical/microcluster/v3/internal/rest/types" - apiTypes "github.com/canonical/microcluster/v3/rest/types" + "github.com/canonical/microcluster/v3/microcluster/types" ) // UpdateServers updates the additional servers config. -func (c *Client) UpdateServers(ctx context.Context, config map[string]apiTypes.ServerConfig) error { +func (c *Client) UpdateServers(ctx context.Context, config map[string]types.ServerConfig) error { queryCtx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() diff --git a/internal/rest/client/hooks.go b/internal/rest/client/hooks.go index 60374c57e..18a651fb2 100644 --- a/internal/rest/client/hooks.go +++ b/internal/rest/client/hooks.go @@ -6,32 +6,31 @@ import ( "github.com/canonical/lxd/shared/api" - internalTypes "github.com/canonical/microcluster/v3/internal/rest/types" - "github.com/canonical/microcluster/v3/rest/types" + "github.com/canonical/microcluster/v3/microcluster/types" ) // RunPreRemoveHook executes the PreRemove hook with the given configuration on the cluster member targeted by this client. -func RunPreRemoveHook(ctx context.Context, c *Client, config internalTypes.HookRemoveMemberOptions) error { +func RunPreRemoveHook(ctx context.Context, c *Client, config types.HookRemoveMemberOptions) error { queryCtx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() - return c.QueryStruct(queryCtx, "POST", internalTypes.InternalEndpoint, &api.NewURL().Path("hooks", string(internalTypes.PreRemove)).URL, config, nil) + return c.QueryStruct(queryCtx, "POST", types.InternalEndpoint, &api.NewURL().Path("hooks", string(types.PreRemove)).URL, config, nil) } // RunPostRemoveHook executes the PostRemove hook with the given configuration on the cluster member targeted by this client. -func RunPostRemoveHook(ctx context.Context, c *Client, config internalTypes.HookRemoveMemberOptions) error { +func RunPostRemoveHook(ctx context.Context, c *Client, config types.HookRemoveMemberOptions) error { queryCtx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() - return c.QueryStruct(queryCtx, "POST", internalTypes.InternalEndpoint, &api.NewURL().Path("hooks", string(internalTypes.PostRemove)).URL, config, nil) + return c.QueryStruct(queryCtx, "POST", types.InternalEndpoint, &api.NewURL().Path("hooks", string(types.PostRemove)).URL, config, nil) } // RunNewMemberHook executes the OnNewMember hook with the given configuration on the cluster member targeted by this client. -func RunNewMemberHook(ctx context.Context, c *Client, config internalTypes.HookNewMemberOptions) error { +func RunNewMemberHook(ctx context.Context, c *Client, config types.HookNewMemberOptions) error { queryCtx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() - return c.QueryStruct(queryCtx, "POST", internalTypes.InternalEndpoint, &api.NewURL().Path("hooks", string(internalTypes.OnNewMember)).URL, config, nil) + return c.QueryStruct(queryCtx, "POST", types.InternalEndpoint, &api.NewURL().Path("hooks", string(types.OnNewMember)).URL, config, nil) } // RunOnDaemonConfigUpdateHook executes the OnDaemonConfigUpdate hook with the given configuration on the cluster member targeted by this client. @@ -39,5 +38,5 @@ func RunOnDaemonConfigUpdateHook(ctx context.Context, c *Client, config *types.D queryCtx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() - return c.QueryStruct(queryCtx, "POST", internalTypes.InternalEndpoint, &api.NewURL().Path("hooks", string(internalTypes.OnDaemonConfigUpdate)).URL, config, nil) + return c.QueryStruct(queryCtx, "POST", types.InternalEndpoint, &api.NewURL().Path("hooks", string(types.OnDaemonConfigUpdate)).URL, config, nil) } diff --git a/internal/rest/client/ready.go b/internal/rest/client/ready.go index 9e5612b69..e79d44b28 100644 --- a/internal/rest/client/ready.go +++ b/internal/rest/client/ready.go @@ -6,7 +6,7 @@ import ( "github.com/canonical/lxd/shared/api" - "github.com/canonical/microcluster/v3/internal/rest/types" + "github.com/canonical/microcluster/v3/microcluster/types" ) // CheckReady returns once the daemon has signalled to the ready channel that it is done setting up. diff --git a/internal/rest/client/shutdown.go b/internal/rest/client/shutdown.go index 9e3292899..5df9466aa 100644 --- a/internal/rest/client/shutdown.go +++ b/internal/rest/client/shutdown.go @@ -6,7 +6,7 @@ import ( "github.com/canonical/lxd/shared/api" - "github.com/canonical/microcluster/v3/internal/rest/types" + "github.com/canonical/microcluster/v3/microcluster/types" ) // ShutdownDaemon begins the daemon shutdown sequence. diff --git a/internal/rest/client/sql.go b/internal/rest/client/sql.go index c8802b8de..6398368e9 100644 --- a/internal/rest/client/sql.go +++ b/internal/rest/client/sql.go @@ -6,7 +6,7 @@ import ( "github.com/canonical/lxd/shared/api" - "github.com/canonical/microcluster/v3/internal/rest/types" + "github.com/canonical/microcluster/v3/microcluster/types" ) // GetSQL gets a SQL dump of the database. diff --git a/internal/rest/client/tokens.go b/internal/rest/client/tokens.go index eb490b2f3..85e3add3b 100644 --- a/internal/rest/client/tokens.go +++ b/internal/rest/client/tokens.go @@ -6,7 +6,7 @@ import ( "github.com/canonical/lxd/shared/api" - "github.com/canonical/microcluster/v3/internal/rest/types" + "github.com/canonical/microcluster/v3/microcluster/types" ) // RequestToken requests a join token with the given name. diff --git a/internal/rest/client/truststore.go b/internal/rest/client/truststore.go index 0c46863d1..7a893ca4a 100644 --- a/internal/rest/client/truststore.go +++ b/internal/rest/client/truststore.go @@ -6,8 +6,7 @@ import ( "github.com/canonical/lxd/shared/api" - internalTypes "github.com/canonical/microcluster/v3/internal/rest/types" - "github.com/canonical/microcluster/v3/rest/types" + "github.com/canonical/microcluster/v3/microcluster/types" ) // AddTrustStoreEntry adds a new record to the truststore on all cluster members. @@ -15,7 +14,7 @@ func AddTrustStoreEntry(ctx context.Context, c *Client, args types.ClusterMember queryCtx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() - return c.QueryStruct(queryCtx, "POST", internalTypes.InternalEndpoint, &api.NewURL().Path("truststore").URL, args, nil) + return c.QueryStruct(queryCtx, "POST", types.InternalEndpoint, &api.NewURL().Path("truststore").URL, args, nil) } // DeleteTrustStoreEntry deletes the record corresponding to the given cluster member from the trust store. @@ -23,5 +22,5 @@ func DeleteTrustStoreEntry(ctx context.Context, c *Client, name string) error { queryCtx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() - return c.QueryStruct(queryCtx, "DELETE", internalTypes.InternalEndpoint, &api.NewURL().Path("truststore", name).URL, nil, nil) + return c.QueryStruct(queryCtx, "DELETE", types.InternalEndpoint, &api.NewURL().Path("truststore", name).URL, nil, nil) } diff --git a/internal/rest/resources/api_1.0.go b/internal/rest/resources/api_1.0.go index 0d1c845a6..ab7346344 100644 --- a/internal/rest/resources/api_1.0.go +++ b/internal/rest/resources/api_1.0.go @@ -3,11 +3,10 @@ package resources import ( "net/http" - internalTypes "github.com/canonical/microcluster/v3/internal/rest/types" internalState "github.com/canonical/microcluster/v3/internal/state" - "github.com/canonical/microcluster/v3/rest" - "github.com/canonical/microcluster/v3/rest/response" - "github.com/canonical/microcluster/v3/rest/types" + "github.com/canonical/microcluster/v3/microcluster/rest" + "github.com/canonical/microcluster/v3/microcluster/rest/response" + "github.com/canonical/microcluster/v3/microcluster/types" "github.com/canonical/microcluster/v3/state" ) @@ -28,7 +27,7 @@ func api10Get(s state.State, r *http.Request) response.Response { return response.SmartError(err) } - return response.SyncResponse(true, internalTypes.Server{ + return response.SyncResponse(true, types.Server{ Name: s.Name(), Address: addrPort, Version: s.Version(), diff --git a/internal/rest/resources/certificates.go b/internal/rest/resources/certificates.go index 0a13bf5fe..e16ff2dd7 100644 --- a/internal/rest/resources/certificates.go +++ b/internal/rest/resources/certificates.go @@ -16,11 +16,11 @@ import ( "github.com/canonical/microcluster/v3/client" "github.com/canonical/microcluster/v3/internal/log" + "github.com/canonical/microcluster/v3/internal/rest/access" internalState "github.com/canonical/microcluster/v3/internal/state" - "github.com/canonical/microcluster/v3/rest" - "github.com/canonical/microcluster/v3/rest/access" - "github.com/canonical/microcluster/v3/rest/response" - "github.com/canonical/microcluster/v3/rest/types" + "github.com/canonical/microcluster/v3/microcluster/rest" + "github.com/canonical/microcluster/v3/microcluster/rest/response" + "github.com/canonical/microcluster/v3/microcluster/types" "github.com/canonical/microcluster/v3/state" ) diff --git a/internal/rest/resources/cluster.go b/internal/rest/resources/cluster.go index 4b3e6b4fd..f6c2a4c1e 100644 --- a/internal/rest/resources/cluster.go +++ b/internal/rest/resources/cluster.go @@ -24,17 +24,16 @@ import ( "golang.org/x/sys/unix" "github.com/canonical/microcluster/v3/client" - "github.com/canonical/microcluster/v3/cluster" + "github.com/canonical/microcluster/v3/internal/cluster" "github.com/canonical/microcluster/v3/internal/log" + "github.com/canonical/microcluster/v3/internal/rest/access" internalClient "github.com/canonical/microcluster/v3/internal/rest/client" - internalTypes "github.com/canonical/microcluster/v3/internal/rest/types" internalState "github.com/canonical/microcluster/v3/internal/state" "github.com/canonical/microcluster/v3/internal/trust" "github.com/canonical/microcluster/v3/internal/utils" - "github.com/canonical/microcluster/v3/rest" - "github.com/canonical/microcluster/v3/rest/access" - "github.com/canonical/microcluster/v3/rest/response" - "github.com/canonical/microcluster/v3/rest/types" + "github.com/canonical/microcluster/v3/microcluster/rest" + "github.com/canonical/microcluster/v3/microcluster/rest/response" + "github.com/canonical/microcluster/v3/microcluster/types" "github.com/canonical/microcluster/v3/state" ) @@ -182,7 +181,7 @@ func clusterPost(s state.State, r *http.Request) response.Response { } localRemote := remotes.RemotesByName()[s.Name()] - tokenResponse := internalTypes.TokenResponse{ + tokenResponse := types.TokenResponse{ ClusterCert: types.X509Certificate{Certificate: clusterCert}, ClusterKey: string(s.ClusterCert().PrivateKey()), @@ -623,7 +622,7 @@ func clusterMemberDelete(s state.State, r *http.Request) response.Response { } // Tell the cluster member to run its PreRemove hook and return. - err = internalClient.RunPreRemoveHook(ctx, c.UseTarget(name), internalTypes.HookRemoveMemberOptions{Force: force}) + err = internalClient.RunPreRemoveHook(ctx, c.UseTarget(name), types.HookRemoveMemberOptions{Force: force}) if err != nil && !force { return response.SmartError(err) } @@ -696,7 +695,7 @@ func clusterMemberDelete(s state.State, r *http.Request) response.Response { return fmt.Errorf("No remote found at address %q run the post-remove hook", c.URL().URL.Host) } - return internalClient.RunPostRemoveHook(ctx, c.Client.UseTarget(remote.Name), internalTypes.HookRemoveMemberOptions{Force: force}) + return internalClient.RunPostRemoveHook(ctx, c.Client.UseTarget(remote.Name), types.HookRemoveMemberOptions{Force: force}) }) if err != nil { return response.SmartError(err) diff --git a/internal/rest/resources/control.go b/internal/rest/resources/control.go index 2ae651229..981f5eec2 100644 --- a/internal/rest/resources/control.go +++ b/internal/rest/resources/control.go @@ -15,15 +15,14 @@ import ( "github.com/canonical/lxd/shared/revert" "github.com/canonical/microcluster/v3/internal/log" + "github.com/canonical/microcluster/v3/internal/rest/access" internalClient "github.com/canonical/microcluster/v3/internal/rest/client" - internalTypes "github.com/canonical/microcluster/v3/internal/rest/types" internalState "github.com/canonical/microcluster/v3/internal/state" "github.com/canonical/microcluster/v3/internal/trust" "github.com/canonical/microcluster/v3/internal/utils" - "github.com/canonical/microcluster/v3/rest" - "github.com/canonical/microcluster/v3/rest/access" - "github.com/canonical/microcluster/v3/rest/response" - "github.com/canonical/microcluster/v3/rest/types" + "github.com/canonical/microcluster/v3/microcluster/rest" + "github.com/canonical/microcluster/v3/microcluster/rest/response" + "github.com/canonical/microcluster/v3/microcluster/types" "github.com/canonical/microcluster/v3/state" ) @@ -39,7 +38,7 @@ func controlPost(state state.State, r *http.Request) response.Response { return response.SmartError(fmt.Errorf("Unable to initialize cluster: %s", status)) } - req := &internalTypes.Control{} + req := &types.Control{} // Parse the request. err := json.NewDecoder(r.Body).Decode(&req) if err != nil { @@ -87,7 +86,7 @@ func controlPost(state state.State, r *http.Request) response.Response { } certNameMatches := slices.Contains(serverCert.DNSNames, req.Name) - var joinInfo *internalTypes.TokenResponse + var joinInfo *types.TokenResponse reverter.Add(func() { // When joining, don't attempt to reset the cluster member if we never received authorization from any cluster members. // This is because we won't have changed any state yet, so resetting the cluster member won't help, and may have its own side-effects. @@ -188,8 +187,8 @@ func controlPost(state state.State, r *http.Request) response.Response { return response.EmptySyncResponse } -func joinWithToken(state state.State, r *http.Request, req *internalTypes.Control) (*internalTypes.TokenResponse, *trust.Remote, error) { - token, err := internalTypes.DecodeToken(req.JoinToken) +func joinWithToken(state state.State, r *http.Request, req *types.Control) (*types.TokenResponse, *trust.Remote, error) { + token, err := types.DecodeToken(req.JoinToken) if err != nil { return nil, nil, err } @@ -232,7 +231,7 @@ func joinWithToken(state state.State, r *http.Request, req *internalTypes.Contro // Get a client to the target address. var lastErr error - var joinInfo *internalTypes.TokenResponse + var joinInfo *types.TokenResponse for _, addr := range token.JoinAddresses { url := api.NewURL().Scheme("https").Host(addr.String()) @@ -291,7 +290,7 @@ func writeCert(dir, prefix string, cert, key, ca []byte) error { return nil } -func setupLocalMember(state state.State, localClusterMember *trust.Remote, joinInfo *internalTypes.TokenResponse) ([]string, error) { +func setupLocalMember(state state.State, localClusterMember *trust.Remote, joinInfo *types.TokenResponse) ([]string, error) { // Set up cluster certificate. err := writeCert(state.FileSystem().StateDir, string(types.ClusterCertificateName), []byte(joinInfo.ClusterCert.String()), []byte(joinInfo.ClusterKey), nil) if err != nil { diff --git a/internal/rest/resources/daemon.go b/internal/rest/resources/daemon.go index 5598372c3..6946cd193 100644 --- a/internal/rest/resources/daemon.go +++ b/internal/rest/resources/daemon.go @@ -8,13 +8,12 @@ import ( "slices" "github.com/canonical/microcluster/v3/client" + "github.com/canonical/microcluster/v3/internal/rest/access" internalClient "github.com/canonical/microcluster/v3/internal/rest/client" - internalTypes "github.com/canonical/microcluster/v3/internal/rest/types" internalState "github.com/canonical/microcluster/v3/internal/state" - "github.com/canonical/microcluster/v3/rest" - "github.com/canonical/microcluster/v3/rest/access" - "github.com/canonical/microcluster/v3/rest/response" - "github.com/canonical/microcluster/v3/rest/types" + "github.com/canonical/microcluster/v3/microcluster/rest" + "github.com/canonical/microcluster/v3/microcluster/rest/response" + "github.com/canonical/microcluster/v3/microcluster/types" "github.com/canonical/microcluster/v3/state" ) @@ -106,7 +105,7 @@ func daemonServersPut(s state.State, r *http.Request) response.Response { remote := remotes.RemoteByAddress(addrPort) if remote == nil { - return fmt.Errorf("No remote found at address %q to run the %q hook", c.URL().URL.Host, internalTypes.OnDaemonConfigUpdate) + return fmt.Errorf("No remote found at address %q to run the %q hook", c.URL().URL.Host, types.OnDaemonConfigUpdate) } return internalClient.RunOnDaemonConfigUpdateHook(ctx, c.Client.UseTarget(remote.Name), daemonConfig.Dump()) diff --git a/internal/rest/resources/database.go b/internal/rest/resources/database.go index 49094ec23..79716d4f0 100644 --- a/internal/rest/resources/database.go +++ b/internal/rest/resources/database.go @@ -6,8 +6,8 @@ import ( "strconv" "github.com/canonical/microcluster/v3/internal/state" - "github.com/canonical/microcluster/v3/rest" - "github.com/canonical/microcluster/v3/rest/response" + "github.com/canonical/microcluster/v3/microcluster/rest" + "github.com/canonical/microcluster/v3/microcluster/rest/response" ) var databaseCmd = rest.Endpoint{ diff --git a/internal/rest/resources/heartbeat.go b/internal/rest/resources/heartbeat.go index e54997740..29c149641 100644 --- a/internal/rest/resources/heartbeat.go +++ b/internal/rest/resources/heartbeat.go @@ -11,13 +11,12 @@ import ( "time" "github.com/canonical/microcluster/v3/client" - "github.com/canonical/microcluster/v3/cluster" + "github.com/canonical/microcluster/v3/internal/cluster" "github.com/canonical/microcluster/v3/internal/log" - internalTypes "github.com/canonical/microcluster/v3/internal/rest/types" internalState "github.com/canonical/microcluster/v3/internal/state" - "github.com/canonical/microcluster/v3/rest" - "github.com/canonical/microcluster/v3/rest/response" - "github.com/canonical/microcluster/v3/rest/types" + "github.com/canonical/microcluster/v3/microcluster/rest" + "github.com/canonical/microcluster/v3/microcluster/rest/response" + "github.com/canonical/microcluster/v3/microcluster/types" "github.com/canonical/microcluster/v3/state" ) @@ -28,7 +27,7 @@ var heartbeatCmd = rest.Endpoint{ } func heartbeatPost(s state.State, r *http.Request) response.Response { - var hbInfo internalTypes.HeartbeatInfo + var hbInfo types.HeartbeatInfo err := json.NewDecoder(r.Body).Decode(&hbInfo) if err != nil { return response.SmartError(err) @@ -91,7 +90,7 @@ func heartbeatPost(s state.State, r *http.Request) response.Response { // beginHeartbeat initiates a heartbeat from the leader node to all other cluster members, if we haven't sent one out // recently. -func beginHeartbeat(ctx context.Context, s state.State, hbReq internalTypes.HeartbeatInfo) response.Response { +func beginHeartbeat(ctx context.Context, s state.State, hbReq types.HeartbeatInfo) response.Response { if s.Address().URL.Host != hbReq.LeaderAddress { return response.SmartError(fmt.Errorf("Attempt to initiate heartbeat from non-leader")) } @@ -178,7 +177,7 @@ func beginHeartbeat(ctx context.Context, s state.State, hbReq internalTypes.Hear clusterMap[s.Address().URL.Host] = leaderEntry // Record the maximum schema version discovered. - hbInfo := internalTypes.HeartbeatInfo{ClusterMembers: clusterMap} + hbInfo := types.HeartbeatInfo{ClusterMembers: clusterMap} for _, node := range clusterMembers { if node.SchemaInternalVersion > hbInfo.MaxSchemaInternal { hbInfo.MaxSchemaInternal = node.SchemaInternalVersion diff --git a/internal/rest/resources/hooks.go b/internal/rest/resources/hooks.go index 59cb7464b..99b0634c2 100644 --- a/internal/rest/resources/hooks.go +++ b/internal/rest/resources/hooks.go @@ -9,12 +9,11 @@ import ( "github.com/gorilla/mux" - internalTypes "github.com/canonical/microcluster/v3/internal/rest/types" + "github.com/canonical/microcluster/v3/internal/rest/access" internalState "github.com/canonical/microcluster/v3/internal/state" - "github.com/canonical/microcluster/v3/rest" - "github.com/canonical/microcluster/v3/rest/access" - "github.com/canonical/microcluster/v3/rest/response" - "github.com/canonical/microcluster/v3/rest/types" + "github.com/canonical/microcluster/v3/microcluster/rest" + "github.com/canonical/microcluster/v3/microcluster/rest/response" + "github.com/canonical/microcluster/v3/microcluster/types" "github.com/canonical/microcluster/v3/state" ) @@ -38,9 +37,9 @@ func hooksPost(s state.State, r *http.Request) response.Response { ctx, cancel := context.WithCancel(r.Context()) defer cancel() - switch internalTypes.HookType(hookTypeStr) { - case internalTypes.PreRemove: - var req internalTypes.HookRemoveMemberOptions + switch types.HookType(hookTypeStr) { + case types.PreRemove: + var req types.HookRemoveMemberOptions err = json.NewDecoder(r.Body).Decode(&req) if err != nil { return response.BadRequest(err) @@ -51,8 +50,8 @@ func hooksPost(s state.State, r *http.Request) response.Response { return response.SmartError(fmt.Errorf("Failed to execute pre-remove hook on cluster member %q: %w", s.Name(), err)) } - case internalTypes.PostRemove: - var req internalTypes.HookRemoveMemberOptions + case types.PostRemove: + var req types.HookRemoveMemberOptions err = json.NewDecoder(r.Body).Decode(&req) if err != nil { return response.BadRequest(err) @@ -63,8 +62,8 @@ func hooksPost(s state.State, r *http.Request) response.Response { return response.SmartError(fmt.Errorf("Failed to execute post-remove hook on cluster member %q: %w", s.Name(), err)) } - case internalTypes.OnNewMember: - var req internalTypes.HookNewMemberOptions + case types.OnNewMember: + var req types.HookNewMemberOptions err = json.NewDecoder(r.Body).Decode(&req) if err != nil { return response.BadRequest(err) @@ -79,7 +78,7 @@ func hooksPost(s state.State, r *http.Request) response.Response { return response.SmartError(fmt.Errorf("Failed to run hook after system %q has joined the cluster: %w", req.NewMember.Name, err)) } - case internalTypes.OnDaemonConfigUpdate: + case types.OnDaemonConfigUpdate: var req types.DaemonConfig err = json.NewDecoder(r.Body).Decode(&req) if err != nil { diff --git a/internal/rest/resources/hooks_test.go b/internal/rest/resources/hooks_test.go index f8f146ce5..9f1a93da2 100644 --- a/internal/rest/resources/hooks_test.go +++ b/internal/rest/resources/hooks_test.go @@ -16,9 +16,8 @@ import ( "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" - internalTypes "github.com/canonical/microcluster/v3/internal/rest/types" "github.com/canonical/microcluster/v3/internal/state" - "github.com/canonical/microcluster/v3/rest/types" + "github.com/canonical/microcluster/v3/microcluster/types" ) type hooksSuite struct { @@ -30,26 +29,26 @@ func TestHooksSuite(t *testing.T) { } func (t *hooksSuite) Test_hooks() { - var ranHook internalTypes.HookType + var ranHook types.HookType var isForce bool s := &state.InternalState{ Context: context.TODO(), InternalName: func() string { return "n0" }, Hooks: &state.Hooks{ PostRemove: func(ctx context.Context, state state.State, force bool) error { - ranHook = internalTypes.PostRemove + ranHook = types.PostRemove isForce = force return nil }, PreRemove: func(ctx context.Context, state state.State, force bool) error { - ranHook = internalTypes.PreRemove + ranHook = types.PreRemove isForce = force return nil }, OnNewMember: func(ctx context.Context, state state.State, newMember types.ClusterMemberLocal) error { - ranHook = internalTypes.OnNewMember + ranHook = types.OnNewMember return nil }, }, @@ -58,55 +57,55 @@ func (t *hooksSuite) Test_hooks() { tests := []struct { name string req any - hookType internalTypes.HookType + hookType types.HookType expectErr bool }{ { name: "Run OnNewMember hook", - req: internalTypes.HookNewMemberOptions{NewMember: types.ClusterMemberLocal{Name: "n1"}}, - hookType: internalTypes.OnNewMember, + req: types.HookNewMemberOptions{NewMember: types.ClusterMemberLocal{Name: "n1"}}, + hookType: types.OnNewMember, expectErr: false, }, { name: "Run PostRemove hook with force", - req: internalTypes.HookRemoveMemberOptions{Force: true}, - hookType: internalTypes.PostRemove, + req: types.HookRemoveMemberOptions{Force: true}, + hookType: types.PostRemove, expectErr: false, }, { name: "Run PostRemove hook without force", - req: internalTypes.HookRemoveMemberOptions{}, - hookType: internalTypes.PostRemove, + req: types.HookRemoveMemberOptions{}, + hookType: types.PostRemove, expectErr: false, }, { name: "Run PreRemove hook with force", - req: internalTypes.HookRemoveMemberOptions{Force: true}, - hookType: internalTypes.PreRemove, + req: types.HookRemoveMemberOptions{Force: true}, + hookType: types.PreRemove, expectErr: false, }, { name: "Run PreRemove hook without force", - req: internalTypes.HookRemoveMemberOptions{}, - hookType: internalTypes.PreRemove, + req: types.HookRemoveMemberOptions{}, + hookType: types.PreRemove, expectErr: false, }, { name: "Fail to run any other hook", - req: internalTypes.HookNewMemberOptions{NewMember: types.ClusterMemberLocal{Name: "n1"}}, - hookType: internalTypes.PostBootstrap, + req: types.HookNewMemberOptions{NewMember: types.ClusterMemberLocal{Name: "n1"}}, + hookType: types.PostBootstrap, expectErr: true, }, { name: "Fail to run a nonexistent hook", - req: internalTypes.HookNewMemberOptions{NewMember: types.ClusterMemberLocal{Name: "n1"}}, + req: types.HookNewMemberOptions{NewMember: types.ClusterMemberLocal{Name: "n1"}}, hookType: "this is not a hook type", expectErr: true, }, { name: "Fail to run a hook with the wrong payload type", - req: internalTypes.HookRemoveMemberOptions{Force: true}, - hookType: internalTypes.OnNewMember, + req: types.HookRemoveMemberOptions{Force: true}, + hookType: types.OnNewMember, expectErr: true, }, } @@ -122,9 +121,9 @@ func (t *hooksSuite) Test_hooks() { URL: &url.URL{}, } - payload, ok := c.req.(internalTypes.HookRemoveMemberOptions) + payload, ok := c.req.(types.HookRemoveMemberOptions) if !ok { - payload, ok := c.req.(internalTypes.HookNewMemberOptions) + payload, ok := c.req.(types.HookNewMemberOptions) t.True(ok) req.Body = io.NopCloser(strings.NewReader(fmt.Sprintf(`{"new_member": {"name": %q}}`, payload.NewMember.Name))) } else { @@ -153,7 +152,7 @@ func (t *hooksSuite) Test_hooks() { t.Equal(api.ErrorResponse, resp.Type) t.NotEqual(api.Success.String(), resp.Status) t.NotEqual(http.StatusOK, resp.StatusCode) - t.Equal(internalTypes.HookType(""), ranHook) + t.Equal(types.HookType(""), ranHook) t.Equal(false, isForce) } } diff --git a/internal/rest/resources/ready.go b/internal/rest/resources/ready.go index a4c8e645d..dcce6fddd 100644 --- a/internal/rest/resources/ready.go +++ b/internal/rest/resources/ready.go @@ -4,10 +4,10 @@ import ( "fmt" "net/http" + "github.com/canonical/microcluster/v3/internal/rest/access" internalState "github.com/canonical/microcluster/v3/internal/state" - "github.com/canonical/microcluster/v3/rest" - "github.com/canonical/microcluster/v3/rest/access" - "github.com/canonical/microcluster/v3/rest/response" + "github.com/canonical/microcluster/v3/microcluster/rest" + "github.com/canonical/microcluster/v3/microcluster/rest/response" "github.com/canonical/microcluster/v3/state" ) diff --git a/internal/rest/resources/resources.go b/internal/rest/resources/resources.go index ab6f8458c..9e956f60a 100644 --- a/internal/rest/resources/resources.go +++ b/internal/rest/resources/resources.go @@ -6,14 +6,13 @@ import ( "strings" "github.com/canonical/microcluster/v3/internal/endpoints" - internalTypes "github.com/canonical/microcluster/v3/internal/rest/types" - "github.com/canonical/microcluster/v3/rest" - "github.com/canonical/microcluster/v3/rest/types" + "github.com/canonical/microcluster/v3/microcluster/rest" + "github.com/canonical/microcluster/v3/microcluster/types" ) // UnixEndpoints are the endpoints available over the unix socket. var UnixEndpoints = rest.Resources{ - PathPrefix: internalTypes.ControlEndpoint, + PathPrefix: types.ControlEndpoint, Endpoints: []rest.Endpoint{ controlCmd, shutdownCmd, @@ -23,7 +22,7 @@ var UnixEndpoints = rest.Resources{ // PublicEndpoints are the /core/1.0 API endpoints available at the listen address. var PublicEndpoints = rest.Resources{ - PathPrefix: internalTypes.PublicEndpoint, + PathPrefix: types.PublicEndpoint, Endpoints: []rest.Endpoint{ api10Cmd, clusterCertificatesCmd, @@ -37,7 +36,7 @@ var PublicEndpoints = rest.Resources{ // InternalEndpoints are the /core/internal API endpoints available at the listen address. var InternalEndpoints = rest.Resources{ - PathPrefix: internalTypes.InternalEndpoint, + PathPrefix: types.InternalEndpoint, Endpoints: []rest.Endpoint{ clusterInternalCmd, clusterMemberInternalCmd, diff --git a/internal/rest/resources/resources_test.go b/internal/rest/resources/resources_test.go index f46fea615..beb8f24eb 100644 --- a/internal/rest/resources/resources_test.go +++ b/internal/rest/resources/resources_test.go @@ -3,7 +3,7 @@ package resources import ( "testing" - "github.com/canonical/microcluster/v3/rest" + "github.com/canonical/microcluster/v3/microcluster/rest" ) var validServers = map[string]rest.Server{ diff --git a/internal/rest/resources/shutdown.go b/internal/rest/resources/shutdown.go index 8918c741d..7e2799b1d 100644 --- a/internal/rest/resources/shutdown.go +++ b/internal/rest/resources/shutdown.go @@ -4,11 +4,11 @@ import ( "fmt" "net/http" + "github.com/canonical/microcluster/v3/internal/rest/access" internalState "github.com/canonical/microcluster/v3/internal/state" - "github.com/canonical/microcluster/v3/rest" - "github.com/canonical/microcluster/v3/rest/access" - "github.com/canonical/microcluster/v3/rest/response" - "github.com/canonical/microcluster/v3/rest/types" + "github.com/canonical/microcluster/v3/microcluster/rest" + "github.com/canonical/microcluster/v3/microcluster/rest/response" + "github.com/canonical/microcluster/v3/microcluster/types" "github.com/canonical/microcluster/v3/state" ) diff --git a/internal/rest/resources/sql.go b/internal/rest/resources/sql.go index 67eaf962a..7c0645de3 100644 --- a/internal/rest/resources/sql.go +++ b/internal/rest/resources/sql.go @@ -13,11 +13,11 @@ import ( "github.com/canonical/microcluster/v3/internal/db/query" "github.com/canonical/microcluster/v3/internal/log" - "github.com/canonical/microcluster/v3/internal/rest/types" + "github.com/canonical/microcluster/v3/internal/rest/access" "github.com/canonical/microcluster/v3/internal/state" - "github.com/canonical/microcluster/v3/rest" - "github.com/canonical/microcluster/v3/rest/access" - "github.com/canonical/microcluster/v3/rest/response" + "github.com/canonical/microcluster/v3/microcluster/rest" + "github.com/canonical/microcluster/v3/microcluster/rest/response" + "github.com/canonical/microcluster/v3/microcluster/types" ) var sqlCmd = rest.Endpoint{ diff --git a/internal/rest/resources/tokens.go b/internal/rest/resources/tokens.go index c8d8c7340..749bfae28 100644 --- a/internal/rest/resources/tokens.go +++ b/internal/rest/resources/tokens.go @@ -12,15 +12,14 @@ import ( "github.com/canonical/lxd/shared" "github.com/gorilla/mux" - "github.com/canonical/microcluster/v3/cluster" + "github.com/canonical/microcluster/v3/internal/cluster" "github.com/canonical/microcluster/v3/internal/log" - internalTypes "github.com/canonical/microcluster/v3/internal/rest/types" + "github.com/canonical/microcluster/v3/internal/rest/access" "github.com/canonical/microcluster/v3/internal/state" "github.com/canonical/microcluster/v3/internal/utils" - "github.com/canonical/microcluster/v3/rest" - "github.com/canonical/microcluster/v3/rest/access" - "github.com/canonical/microcluster/v3/rest/response" - "github.com/canonical/microcluster/v3/rest/types" + "github.com/canonical/microcluster/v3/microcluster/rest" + "github.com/canonical/microcluster/v3/microcluster/rest/response" + "github.com/canonical/microcluster/v3/microcluster/types" ) var tokensCmd = rest.Endpoint{ @@ -37,7 +36,7 @@ var tokenCmd = rest.Endpoint{ } func tokensPost(state state.State, r *http.Request) response.Response { - req := internalTypes.TokenRequest{} + req := types.TokenRequest{} // Parse the request. err := json.NewDecoder(r.Body).Decode(&req) @@ -88,7 +87,7 @@ func tokensPost(state state.State, r *http.Request) response.Response { expiryDate.Time = time.Now().Add(req.ExpireAfter) } - token := internalTypes.Token{ + token := types.Token{ Secret: tokenKey, Fingerprint: shared.CertFingerprint(clusterCert), JoinAddresses: joinAddresses, @@ -130,7 +129,7 @@ func tokensGet(state state.State, r *http.Request) response.Response { joinAddresses = append(joinAddresses, addr) } - var records []internalTypes.TokenRecord + var records []types.TokenRecord err = state.Database().Transaction(r.Context(), func(ctx context.Context, tx *sql.Tx) error { var err error tokens, err := cluster.GetCoreTokenRecords(ctx, tx) @@ -138,7 +137,7 @@ func tokensGet(state state.State, r *http.Request) response.Response { return err } - records = make([]internalTypes.TokenRecord, 0, len(tokens)) + records = make([]types.TokenRecord, 0, len(tokens)) for _, token := range tokens { if token.Expired() { continue diff --git a/internal/rest/resources/truststore.go b/internal/rest/resources/truststore.go index 4675ace00..9bbec4ae8 100644 --- a/internal/rest/resources/truststore.go +++ b/internal/rest/resources/truststore.go @@ -14,12 +14,12 @@ import ( "github.com/canonical/microcluster/v3/client" "github.com/canonical/microcluster/v3/internal/log" + "github.com/canonical/microcluster/v3/internal/rest/access" internalClient "github.com/canonical/microcluster/v3/internal/rest/client" "github.com/canonical/microcluster/v3/internal/trust" - "github.com/canonical/microcluster/v3/rest" - "github.com/canonical/microcluster/v3/rest/access" - "github.com/canonical/microcluster/v3/rest/response" - "github.com/canonical/microcluster/v3/rest/types" + "github.com/canonical/microcluster/v3/microcluster/rest" + "github.com/canonical/microcluster/v3/microcluster/rest/response" + "github.com/canonical/microcluster/v3/microcluster/types" "github.com/canonical/microcluster/v3/state" ) diff --git a/internal/rest/rest.go b/internal/rest/rest.go index ffe58068e..9eaaa98cd 100644 --- a/internal/rest/rest.go +++ b/internal/rest/rest.go @@ -14,14 +14,13 @@ import ( "github.com/canonical/lxd/shared/ws" "github.com/gorilla/mux" - "github.com/canonical/microcluster/v3/cluster" + "github.com/canonical/microcluster/v3/internal/cluster" "github.com/canonical/microcluster/v3/internal/log" - internalAccess "github.com/canonical/microcluster/v3/internal/rest/access" + "github.com/canonical/microcluster/v3/internal/rest/access" "github.com/canonical/microcluster/v3/internal/rest/client" internalState "github.com/canonical/microcluster/v3/internal/state" - "github.com/canonical/microcluster/v3/rest" - "github.com/canonical/microcluster/v3/rest/access" - "github.com/canonical/microcluster/v3/rest/response" + "github.com/canonical/microcluster/v3/microcluster/rest" + "github.com/canonical/microcluster/v3/microcluster/rest/response" "github.com/canonical/microcluster/v3/state" ) @@ -169,7 +168,7 @@ func handleDatabaseRequest(action rest.EndpointAction, state state.State, w http return response.Forbidden(nil) } - trustedReq, ok := trusted.(internalAccess.TrustedRequest) + trustedReq, ok := trusted.(access.TrustedRequest) if !ok { return response.Forbidden(nil) } @@ -278,7 +277,7 @@ func HandleEndpoint(state state.State, mux *mux.Router, version string, e rest.E if err != nil && !errors.As(err, &access.ErrInvalidHost{}) { resp = response.Forbidden(fmt.Errorf("Failed to authenticate request: %w", err)) } else { - r = internalAccess.SetRequestAuthentication(r, trusted) + r = access.SetRequestAuthentication(r, trusted) switch r.Method { case "GET": diff --git a/internal/rest/types/heartbeat.go b/internal/rest/types/heartbeat.go deleted file mode 100644 index b9af0a814..000000000 --- a/internal/rest/types/heartbeat.go +++ /dev/null @@ -1,16 +0,0 @@ -package types - -import ( - "github.com/canonical/microcluster/v3/rest/types" -) - -// HeartbeatInfo represents information about the cluster sent out by the leader of the cluster to other members. -// If BeginRound is set, a new heartbeat will initiate. -type HeartbeatInfo struct { - BeginRound bool `json:"begin_round" yaml:"begin_round"` - MaxSchemaInternal uint64 `json:"max_schema_internal" yaml:"max_schema_internal"` - MaxSchemaExternal uint64 `json:"max_schema_external" yaml:"max_schema_external"` - ClusterMembers map[string]types.ClusterMember `json:"cluster_members" yaml:"cluster_members"` - LeaderAddress string `json:"leader_address" yaml:"leader_address"` - DqliteRoles map[string]string `json:"dqlite_roles" yaml:"dqlite_roles"` -} diff --git a/internal/state/hooks.go b/internal/state/hooks.go index 6de480f63..ddd124b3f 100644 --- a/internal/state/hooks.go +++ b/internal/state/hooks.go @@ -3,7 +3,7 @@ package state import ( "context" - "github.com/canonical/microcluster/v3/rest/types" + "github.com/canonical/microcluster/v3/microcluster/types" ) // Hooks holds customizable functions that can be called at varying points by the daemon to. diff --git a/internal/state/state.go b/internal/state/state.go index 147cfcbb5..35ac3a3bb 100644 --- a/internal/state/state.go +++ b/internal/state/state.go @@ -16,7 +16,7 @@ import ( internalClient "github.com/canonical/microcluster/v3/internal/rest/client" "github.com/canonical/microcluster/v3/internal/sys" "github.com/canonical/microcluster/v3/internal/trust" - "github.com/canonical/microcluster/v3/rest/types" + "github.com/canonical/microcluster/v3/microcluster/types" ) // State exposes the internal daemon state for use with extended API handlers. diff --git a/internal/sys/os.go b/internal/sys/os.go index 826456d4d..934c68ff1 100644 --- a/internal/sys/os.go +++ b/internal/sys/os.go @@ -9,7 +9,7 @@ import ( "github.com/canonical/lxd/shared" "github.com/canonical/lxd/shared/api" - "github.com/canonical/microcluster/v3/rest/types" + "github.com/canonical/microcluster/v3/microcluster/types" ) // OS contains fields and methods for interacting with the state directory. diff --git a/internal/trust/remotes.go b/internal/trust/remotes.go index 0939c3d9d..d6c0ecbcc 100644 --- a/internal/trust/remotes.go +++ b/internal/trust/remotes.go @@ -17,7 +17,7 @@ import ( "github.com/canonical/microcluster/v3/client" internalClient "github.com/canonical/microcluster/v3/internal/rest/client" - "github.com/canonical/microcluster/v3/rest/types" + "github.com/canonical/microcluster/v3/microcluster/types" ) // Remotes is a convenient alias as we will often deal with groups of yaml files. diff --git a/microcluster/app.go b/microcluster/app.go index ebeaf0dac..4c831052a 100644 --- a/microcluster/app.go +++ b/microcluster/app.go @@ -17,14 +17,12 @@ import ( "golang.org/x/sys/unix" "github.com/canonical/microcluster/v3/client" - "github.com/canonical/microcluster/v3/cluster" "github.com/canonical/microcluster/v3/internal/daemon" "github.com/canonical/microcluster/v3/internal/log" "github.com/canonical/microcluster/v3/internal/recover" internalClient "github.com/canonical/microcluster/v3/internal/rest/client" - internalTypes "github.com/canonical/microcluster/v3/internal/rest/types" "github.com/canonical/microcluster/v3/internal/sys" - "github.com/canonical/microcluster/v3/rest/types" + "github.com/canonical/microcluster/v3/microcluster/types" ) // DaemonArgs are the data needed to start a MicroCluster daemon. @@ -104,14 +102,14 @@ func (m *MicroCluster) Start(ctx context.Context, daemonArgs DaemonArgs) error { } // Status returns basic status information about the cluster. -func (m *MicroCluster) Status(ctx context.Context) (*internalTypes.Server, error) { +func (m *MicroCluster) Status(ctx context.Context) (*types.Server, error) { c, err := m.LocalClient() if err != nil { return nil, err } - server := internalTypes.Server{} - err = c.QueryStruct(ctx, "GET", internalTypes.PublicEndpoint, nil, nil, &server) + server := types.Server{} + err = c.QueryStruct(ctx, "GET", types.PublicEndpoint, nil, nil, &server) if err != nil { return nil, fmt.Errorf("Failed to get cluster status: %w", err) } @@ -194,7 +192,7 @@ func (m *MicroCluster) NewCluster(ctx context.Context, name string, address stri return fmt.Errorf("Received invalid address %q: %w", address, err) } - return c.ControlDaemon(ctx, internalTypes.Control{Bootstrap: true, Address: addr, Name: name, InitConfig: config}) + return c.ControlDaemon(ctx, types.Control{Bootstrap: true, Address: addr, Name: name, InitConfig: config}) } // JoinCluster joins an existing cluster with a join token supplied by an existing cluster member. @@ -209,7 +207,7 @@ func (m *MicroCluster) JoinCluster(ctx context.Context, name string, address str return fmt.Errorf("Received invalid address %q: %w", address, err) } - return c.ControlDaemon(ctx, internalTypes.Control{JoinToken: token, Address: addr, Name: name, InitConfig: initConfig}) + return c.ControlDaemon(ctx, types.Control{JoinToken: token, Address: addr, Name: name, InitConfig: initConfig}) } // GetDqliteClusterMembers retrieves the current local cluster configuration @@ -217,7 +215,7 @@ func (m *MicroCluster) JoinCluster(ctx context.Context, name string, address str // database. // This is primarily intended for modifying the cluster configuration via // MicroCluster.RecoverFromQuorumLoss. -func (m *MicroCluster) GetDqliteClusterMembers() ([]cluster.DqliteMember, error) { +func (m *MicroCluster) GetDqliteClusterMembers() ([]types.DqliteMember, error) { return recover.GetDqliteClusterMembers(m.FileSystem) } @@ -238,7 +236,7 @@ func (m *MicroCluster) GetDqliteClusterMembers() ([]cluster.DqliteMember, error) // // On start, Microcluster will automatically check for & load the recovery // tarball. A database backup will be taken before the load. -func (m *MicroCluster) RecoverFromQuorumLoss(members []cluster.DqliteMember) (string, error) { +func (m *MicroCluster) RecoverFromQuorumLoss(members []types.DqliteMember) (string, error) { // Double check to make sure the cluster configuration has actually changed oldMembers, err := m.GetDqliteClusterMembers() if err != nil { @@ -276,7 +274,7 @@ func (m *MicroCluster) NewJoinToken(ctx context.Context, name string, expireAfte } // ListJoinTokens lists all the join tokens currently available for use. -func (m *MicroCluster) ListJoinTokens(ctx context.Context) ([]internalTypes.TokenRecord, error) { +func (m *MicroCluster) ListJoinTokens(ctx context.Context) ([]types.TokenRecord, error) { c, err := m.LocalClient() if err != nil { return nil, err @@ -378,7 +376,7 @@ func (m *MicroCluster) RemoteClientWithCert(address string, cert *x509.Certifica } // SQL performs either a GET or POST on /internal/sql with a given query. This is a useful helper for using direct SQL. -func (m *MicroCluster) SQL(ctx context.Context, query string) (string, *internalTypes.SQLBatch, error) { +func (m *MicroCluster) SQL(ctx context.Context, query string) (string, *types.SQLBatch, error) { if query == "-" { // Read from stdin bytes, err := io.ReadAll(os.Stdin) @@ -403,7 +401,7 @@ func (m *MicroCluster) SQL(ctx context.Context, query string) (string, *internal return dump.Text, nil, nil } - data := internalTypes.SQLQuery{ + data := types.SQLQuery{ Query: query, } diff --git a/cluster/db/objects.go b/microcluster/db/objects.go similarity index 100% rename from cluster/db/objects.go rename to microcluster/db/objects.go diff --git a/cluster/db/objects_test.go b/microcluster/db/objects_test.go similarity index 97% rename from cluster/db/objects_test.go rename to microcluster/db/objects_test.go index 993c31b6b..025359903 100644 --- a/cluster/db/objects_test.go +++ b/microcluster/db/objects_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/canonical/microcluster/v3/cluster/db" + "github.com/canonical/microcluster/v3/microcluster/db" ) // Exercise possible failure modes. diff --git a/cluster/db/schema.go b/microcluster/db/schema.go similarity index 100% rename from cluster/db/schema.go rename to microcluster/db/schema.go diff --git a/cluster/db/slices.go b/microcluster/db/slices.go similarity index 100% rename from cluster/db/slices.go rename to microcluster/db/slices.go diff --git a/cluster/db/slices_test.go b/microcluster/db/slices_test.go similarity index 97% rename from cluster/db/slices_test.go rename to microcluster/db/slices_test.go index 5d4ad6d7b..b90e3d4eb 100644 --- a/cluster/db/slices_test.go +++ b/microcluster/db/slices_test.go @@ -8,7 +8,7 @@ import ( _ "github.com/mattn/go-sqlite3" "github.com/stretchr/testify/assert" - "github.com/canonical/microcluster/v3/cluster/db" + "github.com/canonical/microcluster/v3/microcluster/db" ) // Exercise possible failure modes. diff --git a/cluster/stmt.go b/microcluster/db/stmt.go similarity index 98% rename from cluster/stmt.go rename to microcluster/db/stmt.go index 602c0fd40..86b62a451 100644 --- a/cluster/stmt.go +++ b/microcluster/db/stmt.go @@ -1,4 +1,4 @@ -package cluster +package db import ( "database/sql" diff --git a/rest/response/response.go b/microcluster/rest/response/response.go similarity index 100% rename from rest/response/response.go rename to microcluster/rest/response/response.go diff --git a/rest/response/smart.go b/microcluster/rest/response/smart.go similarity index 100% rename from rest/response/smart.go rename to microcluster/rest/response/smart.go diff --git a/rest/rest.go b/microcluster/rest/rest.go similarity index 95% rename from rest/rest.go rename to microcluster/rest/rest.go index f69553b8f..6eb142011 100644 --- a/rest/rest.go +++ b/microcluster/rest/rest.go @@ -4,8 +4,8 @@ import ( "net/http" "time" - "github.com/canonical/microcluster/v3/rest/response" - "github.com/canonical/microcluster/v3/rest/types" + "github.com/canonical/microcluster/v3/microcluster/rest/response" + "github.com/canonical/microcluster/v3/microcluster/types" "github.com/canonical/microcluster/v3/state" ) diff --git a/rest/types/addrport.go b/microcluster/types/addrport.go similarity index 100% rename from rest/types/addrport.go rename to microcluster/types/addrport.go diff --git a/rest/types/addrport_test.go b/microcluster/types/addrport_test.go similarity index 100% rename from rest/types/addrport_test.go rename to microcluster/types/addrport_test.go diff --git a/rest/types/certificate.go b/microcluster/types/certificate.go similarity index 100% rename from rest/types/certificate.go rename to microcluster/types/certificate.go diff --git a/rest/types/cluster.go b/microcluster/types/cluster.go similarity index 100% rename from rest/types/cluster.go rename to microcluster/types/cluster.go diff --git a/rest/types/config.go b/microcluster/types/config.go similarity index 100% rename from rest/types/config.go rename to microcluster/types/config.go diff --git a/internal/rest/types/control.go b/microcluster/types/control.go similarity index 75% rename from internal/rest/types/control.go rename to microcluster/types/control.go index 1851723b1..c6c33a6ed 100644 --- a/internal/rest/types/control.go +++ b/microcluster/types/control.go @@ -1,14 +1,10 @@ package types -import ( - "github.com/canonical/microcluster/v3/rest/types" -) - // Control represents the arguments that can be used to initialize/shutdown the daemon. type Control struct { Bootstrap bool `json:"bootstrap" yaml:"bootstrap"` InitConfig map[string]string `json:"config" yaml:"config"` JoinToken string `json:"join_token" yaml:"join_token"` - Address types.AddrPort `json:"address" yaml:"address"` + Address AddrPort `json:"address" yaml:"address"` Name string `json:"name" yaml:"name"` } diff --git a/rest/types/database.go b/microcluster/types/database.go similarity index 100% rename from rest/types/database.go rename to microcluster/types/database.go diff --git a/cluster/recover.go b/microcluster/types/dqlite.go similarity index 98% rename from cluster/recover.go rename to microcluster/types/dqlite.go index bb35373a2..9732b11ad 100644 --- a/cluster/recover.go +++ b/microcluster/types/dqlite.go @@ -1,4 +1,4 @@ -package cluster +package types import ( "fmt" diff --git a/rest/types/endpoint.go b/microcluster/types/endpoint.go similarity index 100% rename from rest/types/endpoint.go rename to microcluster/types/endpoint.go diff --git a/microcluster/types/heartbeat.go b/microcluster/types/heartbeat.go new file mode 100644 index 000000000..bfebe4d9a --- /dev/null +++ b/microcluster/types/heartbeat.go @@ -0,0 +1,12 @@ +package types + +// HeartbeatInfo represents information about the cluster sent out by the leader of the cluster to other members. +// If BeginRound is set, a new heartbeat will initiate. +type HeartbeatInfo struct { + BeginRound bool `json:"begin_round" yaml:"begin_round"` + MaxSchemaInternal uint64 `json:"max_schema_internal" yaml:"max_schema_internal"` + MaxSchemaExternal uint64 `json:"max_schema_external" yaml:"max_schema_external"` + ClusterMembers map[string]ClusterMember `json:"cluster_members" yaml:"cluster_members"` + LeaderAddress string `json:"leader_address" yaml:"leader_address"` + DqliteRoles map[string]string `json:"dqlite_roles" yaml:"dqlite_roles"` +} diff --git a/internal/rest/types/hooks.go b/microcluster/types/hooks.go similarity index 93% rename from internal/rest/types/hooks.go rename to microcluster/types/hooks.go index adda8e098..681cd9858 100644 --- a/internal/rest/types/hooks.go +++ b/microcluster/types/hooks.go @@ -1,7 +1,5 @@ package types -import "github.com/canonical/microcluster/v3/rest/types" - // HookType represents the various types of hooks available to microcluster. type HookType string @@ -48,5 +46,5 @@ type HookRemoveMemberOptions struct { // HookNewMemberOptions holds configuration pertaining to the OnNewMember hook. type HookNewMemberOptions struct { // Name is the name of the new cluster member that joined the cluster, triggering this hook. - NewMember types.ClusterMemberLocal `json:"new_member" yaml:"new_member"` + NewMember ClusterMemberLocal `json:"new_member" yaml:"new_member"` } diff --git a/internal/rest/types/server.go b/microcluster/types/server.go similarity index 53% rename from internal/rest/types/server.go rename to microcluster/types/server.go index 092c27e2b..9bc821ad6 100644 --- a/internal/rest/types/server.go +++ b/microcluster/types/server.go @@ -1,26 +1,30 @@ package types -import ( - "github.com/canonical/microcluster/v3/internal/extensions" - "github.com/canonical/microcluster/v3/rest/types" +import "github.com/canonical/microcluster/v3/internal/extensions" + +const ( + // PublicEndpoint - Internally managed APIs. + PublicEndpoint EndpointPrefix = "core/1.0" + + // InternalEndpoint - All internal endpoints restricted to trusted servers. + InternalEndpoint EndpointPrefix = "core/internal" + + // ControlEndpoint - All internal endpoints available on the local unix socket. + ControlEndpoint EndpointPrefix = "core/control" ) +// ServerConfig represents the mutable fields of an additional network listener. +type ServerConfig struct { + // Address is the server listen address. + // Example: 127.0.0.1:9000 + Address AddrPort `json:"address" yaml:"address"` +} + // Server represents server status information. type Server struct { Name string `json:"name" yaml:"name"` - Address types.AddrPort `json:"address" yaml:"address"` + Address AddrPort `json:"address" yaml:"address"` Version string `json:"version" yaml:"version"` Ready bool `json:"ready" yaml:"ready"` Extensions extensions.Extensions `json:"extensions" yaml:"extensions"` } - -const ( - // PublicEndpoint - Internally managed APIs. - PublicEndpoint types.EndpointPrefix = "core/1.0" - - // InternalEndpoint - All internal endpoints restricted to trusted servers. - InternalEndpoint types.EndpointPrefix = "core/internal" - - // ControlEndpoint - All internal endpoints available on the local unix socket. - ControlEndpoint types.EndpointPrefix = "core/control" -) diff --git a/internal/rest/types/sql.go b/microcluster/types/sql.go similarity index 100% rename from internal/rest/types/sql.go rename to microcluster/types/sql.go diff --git a/internal/rest/types/tokens.go b/microcluster/types/tokens.go similarity index 85% rename from internal/rest/types/tokens.go rename to microcluster/types/tokens.go index 5f60bdd2f..0cd83a0b6 100644 --- a/internal/rest/types/tokens.go +++ b/microcluster/types/tokens.go @@ -4,8 +4,6 @@ import ( "encoding/base64" "encoding/json" "time" - - "github.com/canonical/microcluster/v3/rest/types" ) // TokenRequest holds information for requesting a join token. @@ -24,24 +22,24 @@ type TokenRecord struct { // TokenResponse holds the information for connecting to a cluster by a node with a valid join token. type TokenResponse struct { // ClusterCert is the public key used across the cluster. - ClusterCert types.X509Certificate `json:"cluster_cert" yaml:"cluster_cert"` + ClusterCert X509Certificate `json:"cluster_cert" yaml:"cluster_cert"` // ClusterKey is the private key used across the cluster. ClusterKey string `json:"cluster_key" yaml:"cluster_key"` // ClusterMembers is the full list of cluster members that are currently present and available in the cluster. // The joiner supplies this list to dqlite so that it can start its database. - ClusterMembers []types.ClusterMemberLocal `json:"cluster_members" yaml:"cluster_members"` + ClusterMembers []ClusterMemberLocal `json:"cluster_members" yaml:"cluster_members"` // ClusterAdditionalCerts is the full list of certificates added for additional listeners. - ClusterAdditionalCerts map[string]types.KeyPair + ClusterAdditionalCerts map[string]KeyPair // TrustedMember contains the address of the existing cluster member // who was dqlite leader at the time that the joiner supplied its join token. // // The trusted member will have already recorded the joiner's information in // its local truststore, and thus will trust requests from the joiner prior to fully joining. - TrustedMember types.ClusterMemberLocal `json:"trusted_member" yaml:"trusted_member"` + TrustedMember ClusterMemberLocal `json:"trusted_member" yaml:"trusted_member"` } // Token holds the information that is presented to the joining node when requesting a token. @@ -55,7 +53,7 @@ type Token struct { // JoinAddresses is the list of addresses of the existing cluster members that the joiner may supply the token to. // Internally, the first system to accept the token will forward it to the dqlite leader. - JoinAddresses []types.AddrPort `json:"join_addresses" yaml:"join_addresses"` + JoinAddresses []AddrPort `json:"join_addresses" yaml:"join_addresses"` } func (t Token) String() (string, error) { diff --git a/rest/access/handlers.go b/rest/access/handlers.go deleted file mode 100644 index 05090db83..000000000 --- a/rest/access/handlers.go +++ /dev/null @@ -1,146 +0,0 @@ -package access - -import ( - "context" - "crypto/subtle" - "crypto/x509" - "fmt" - "log/slog" - "net/http" - "time" - - "github.com/canonical/lxd/shared/api" - - "github.com/canonical/microcluster/v3/internal/endpoints" - "github.com/canonical/microcluster/v3/internal/log" - "github.com/canonical/microcluster/v3/internal/rest/access" - "github.com/canonical/microcluster/v3/internal/rest/client" - internalState "github.com/canonical/microcluster/v3/internal/state" - "github.com/canonical/microcluster/v3/rest/response" - "github.com/canonical/microcluster/v3/rest/types" - "github.com/canonical/microcluster/v3/state" -) - -// ErrInvalidHost is used to indicate that a request host is invalid. -type ErrInvalidHost struct { - error -} - -// Unwrap implements xerrors.Unwrap for ErrInvalidHost. -func (e ErrInvalidHost) Unwrap() error { - return e.error -} - -// AllowAuthenticated checks if the request is trusted by extracting access.TrustedRequest from the request context. -// This handler is used as an access handler by default if AllowUntrusted is false on a rest.EndpointAction. -func AllowAuthenticated(state state.State, r *http.Request) (bool, response.Response) { - trusted := r.Context().Value(client.CtxAccess) - if trusted == nil { - return false, response.Forbidden(nil) - } - - trustedReq, ok := trusted.(access.TrustedRequest) - if !ok { - return false, response.Forbidden(nil) - } - - if !trustedReq.Trusted { - return false, response.Forbidden(nil) - } - - return true, nil -} - -// certificateInDate returns an error if the current time is before the certificates "not before", or after the -// certificates "not after". -func certificateInDate(cert x509.Certificate) error { - now := time.Now() - if now.Before(cert.NotBefore) { - return api.StatusErrorf(http.StatusUnauthorized, "Certificate is not yet valid") - } - - if now.After(cert.NotAfter) { - return api.StatusErrorf(http.StatusUnauthorized, "Certificate has expired") - } - - return nil -} - -// checkMutualTLS checks whether the given certificate is valid and is present in the given trustedCerts map. -// Returns true if the certificate is trusted, and the fingerprint of the certificate. -func checkMutualTLS(ctx context.Context, cert x509.Certificate, trustedCerts map[string]x509.Certificate) (bool, string) { - err := certificateInDate(cert) - if err != nil { - return false, "" - } - - logger, err := log.LoggerFromContext(ctx) - if err != nil { - // We failed to get the logger so we can't log the error. - return false, "" - } - - // Check whether client certificate is in the map of trusted certs. - for fingerprint, v := range trustedCerts { - if subtle.ConstantTimeCompare(cert.Raw, v.Raw) == 1 { - logger.Debug("Matched trusted cert", slog.String("fingerprint", fingerprint), slog.String("subject", v.Subject.String())) - return true, fingerprint - } - } - - return false, "" -} - -// Authenticate ensures the request certificates are trusted against the given set of trusted certificates. -// - Requests over the unix socket are always allowed. -// - HTTP requests require the TLS Peer certificate to match an entry in the supplied map of certificates. -func Authenticate(state state.State, r *http.Request, hostAddress string, trustedCerts map[string]x509.Certificate) (bool, error) { - if r.RemoteAddr == "@" { - return true, nil - } - - intState, err := internalState.ToInternal(state) - if err != nil { - return false, err - } - - logger, err := log.LoggerFromContext(r.Context()) - if err != nil { - return false, err - } - - // Check if it's the core API listener and if it is using the server.crt. - // This indicates that the daemon is in a pre-init state and is listening on the PreInitListenAddress. - endpoint := intState.Endpoints.Get(endpoints.EndpointsCore) - network, ok := endpoint.(*endpoints.Network) - if ok { - if state.ServerCert().Fingerprint() == network.TLS().Fingerprint() { - logger.Info("Allowing unauthenticated request to un-initialized system") - return true, nil - } - } - - // Ensure the given host address is valid. - hostAddrPort, err := types.ParseAddrPort(hostAddress) - if err != nil { - return false, fmt.Errorf("Invalid host address %q", hostAddress) - } - - switch r.Host { - case hostAddrPort.WithZone("").String(): - if r.TLS != nil { - for _, cert := range r.TLS.PeerCertificates { - trusted, fingerprint := checkMutualTLS(r.Context(), *cert, trustedCerts) - if trusted { - logger.Debug("Authenticated request", slog.String("origin", r.RemoteAddr), slog.String("destination", r.URL.String()), slog.String("fingerprint", fingerprint)) - return trusted, nil - } - } - } - - default: - return false, ErrInvalidHost{error: fmt.Errorf("Invalid request address %q", r.Host)} - } - - return false, nil -} diff --git a/rest/types/server.go b/rest/types/server.go deleted file mode 100644 index aa238c046..000000000 --- a/rest/types/server.go +++ /dev/null @@ -1,8 +0,0 @@ -package types - -// ServerConfig represents the mutable fields of an additional network listener. -type ServerConfig struct { - // Address is the server listen address. - // Example: 127.0.0.1:9000 - Address AddrPort `json:"address" yaml:"address"` -}