@@ -57,7 +57,6 @@ func TestHandlers_CreateBlueprint(t *testing.T) {
5757 if runtime .GOOS == "darwin" {
5858 t .Skip ("crypt() not supported on darwin" )
5959 }
60-
6160 var jsonResp v1.HTTPErrorList
6261 ctx := context .Background ()
6362 dbase , srvURL , shutdownFn := makeTestServer (t , nil )
@@ -1877,3 +1876,209 @@ func TestBlueprintUpdateAddsSnapshot(t *testing.T) {
18771876
18781877 require .NoError (t , srv .DB .DeleteBlueprint (context .Background (), created .Id , "000000" ))
18791878}
1879+
1880+ func TestHandlers_BlueprintCustomizationValidation (t * testing.T ) {
1881+ if runtime .GOOS == "darwin" {
1882+ t .Skip ("crypt() not supported on darwin" )
1883+ }
1884+
1885+ _ , srvURL , shutdownFn := makeTestServer (t , nil )
1886+ defer shutdownFn (t )
1887+
1888+ // Test cases structure
1889+ testCases := []struct {
1890+ name string
1891+ customizations map [string ]interface {}
1892+ expectError bool
1893+ errorTitle string
1894+ }{
1895+ // Files - Positive cases
1896+ {
1897+ name : "valid file customization - simple" ,
1898+ customizations : map [string ]interface {}{
1899+ "files" : []map [string ]interface {}{
1900+ {"path" : "/etc/myconfig.conf" , "data" : "config content" },
1901+ },
1902+ },
1903+ expectError : false ,
1904+ },
1905+ {
1906+ name : "valid file customization - with mode and user" ,
1907+ customizations : map [string ]interface {}{
1908+ "files" : []map [string ]interface {}{
1909+ {
1910+ "path" : "/opt/app/config.json" ,
1911+ "data" : `{"key": "value"}` ,
1912+ "mode" : "644" ,
1913+ "user" : "root" ,
1914+ "group" : "root" ,
1915+ },
1916+ },
1917+ },
1918+ expectError : false ,
1919+ },
1920+ // Files - Negative cases
1921+ {
1922+ name : "invalid file path - relative" ,
1923+ customizations : map [string ]interface {}{
1924+ "files" : []map [string ]interface {}{
1925+ {"path" : "config.txt" , "data" : "content" },
1926+ },
1927+ },
1928+ expectError : true ,
1929+ errorTitle : "Invalid file customization" ,
1930+ },
1931+ {
1932+ name : "invalid file path - trailing slash" ,
1933+ customizations : map [string ]interface {}{
1934+ "files" : []map [string ]interface {}{
1935+ {"path" : "/etc/config/" , "data" : "content" },
1936+ },
1937+ },
1938+ expectError : true ,
1939+ errorTitle : "Invalid file customization" ,
1940+ },
1941+ // Directories - Positive cases
1942+ {
1943+ name : "valid directory customization - simple" ,
1944+ customizations : map [string ]interface {}{
1945+ "directories" : []map [string ]interface {}{
1946+ {"path" : "/opt/myapp" },
1947+ },
1948+ },
1949+ expectError : false ,
1950+ },
1951+ {
1952+ name : "valid directory customization - with mode and ensure_parents" ,
1953+ customizations : map [string ]interface {}{
1954+ "directories" : []map [string ]interface {}{
1955+ {
1956+ "path" : "/var/lib/myservice" ,
1957+ "mode" : "755" ,
1958+ "user" : "service" ,
1959+ "group" : "service" ,
1960+ "ensure_parents" : true ,
1961+ },
1962+ },
1963+ },
1964+ expectError : false ,
1965+ },
1966+ // Directories - Negative cases
1967+ {
1968+ name : "invalid directory path - relative" ,
1969+ customizations : map [string ]interface {}{
1970+ "directories" : []map [string ]interface {}{
1971+ {"path" : "mydir" },
1972+ },
1973+ },
1974+ expectError : true ,
1975+ errorTitle : "Invalid directory customization" ,
1976+ },
1977+ {
1978+ name : "invalid directory path - trailing slash" ,
1979+ customizations : map [string ]interface {}{
1980+ "directories" : []map [string ]interface {}{
1981+ {"path" : "/opt/myapp/" },
1982+ },
1983+ },
1984+ expectError : true ,
1985+ errorTitle : "Invalid directory customization" ,
1986+ },
1987+ // Filesystem - Positive cases
1988+ {
1989+ name : "valid filesystem customization - root" ,
1990+ customizations : map [string ]interface {}{
1991+ "filesystem" : []map [string ]interface {}{
1992+ {"mountpoint" : "/" , "min_size" : 10737418240 }, // 10GB
1993+ },
1994+ },
1995+ expectError : false ,
1996+ },
1997+ {
1998+ name : "valid filesystem customization - var with size" ,
1999+ customizations : map [string ]interface {}{
2000+ "filesystem" : []map [string ]interface {}{
2001+ {"mountpoint" : "/var" , "min_size" : 5368709120 }, // 5GB
2002+ },
2003+ },
2004+ expectError : false ,
2005+ },
2006+ // Filesystem - Negative cases
2007+ {
2008+ name : "invalid filesystem mountpoint - relative" ,
2009+ customizations : map [string ]interface {}{
2010+ "filesystem" : []map [string ]interface {}{
2011+ {"mountpoint" : "var" , "min_size" : 1073741824 },
2012+ },
2013+ },
2014+ expectError : true ,
2015+ errorTitle : "Invalid filesystem customization" ,
2016+ },
2017+ {
2018+ name : "invalid filesystem mountpoint - non-canonical" ,
2019+ customizations : map [string ]interface {}{
2020+ "filesystem" : []map [string ]interface {}{
2021+ {"mountpoint" : "/var/../tmp" , "min_size" : 1073741824 },
2022+ },
2023+ },
2024+ expectError : true ,
2025+ errorTitle : "Invalid filesystem customization" ,
2026+ },
2027+ {
2028+ name : "invalid filesystem min_size - too small" ,
2029+ customizations : map [string ]interface {}{
2030+ "filesystem" : []map [string ]interface {}{
2031+ {"mountpoint" : "/var" , "min_size" : 512 }, // 512 bytes, less than 1MB
2032+ },
2033+ },
2034+ expectError : true ,
2035+ errorTitle : "Invalid filesystem customization" ,
2036+ },
2037+ }
2038+
2039+ // Run test cases
2040+ for _ , tc := range testCases {
2041+ t .Run (tc .name , func (t * testing.T ) {
2042+ // Create blueprint request with test customizations
2043+ body := map [string ]interface {}{
2044+ "name" : fmt .Sprintf ("test-blueprint-%d" , time .Now ().UnixNano ()),
2045+ "description" : "Test blueprint for validation" ,
2046+ "customizations" : tc .customizations ,
2047+ "distribution" : "centos-9" ,
2048+ "image_requests" : []map [string ]interface {}{
2049+ {
2050+ "architecture" : "x86_64" ,
2051+ "image_type" : "aws" ,
2052+ "upload_request" : map [string ]interface {}{
2053+ "type" : "aws" ,
2054+ "options" : map [string ]interface {}{
2055+ "share_with_accounts" : []string {"test-account" },
2056+ },
2057+ },
2058+ },
2059+ },
2060+ }
2061+
2062+ statusCode , resp := tutils .PostResponseBody (t , srvURL + "/api/image-builder/v1/blueprints" , body )
2063+
2064+ if tc .expectError {
2065+ // Should return validation error
2066+ require .Equal (t , http .StatusUnprocessableEntity , statusCode )
2067+
2068+ var jsonResp v1.HTTPErrorList
2069+ err := json .Unmarshal ([]byte (resp ), & jsonResp )
2070+ require .NoError (t , err )
2071+ require .NotEmpty (t , jsonResp .Errors )
2072+ require .Equal (t , tc .errorTitle , jsonResp .Errors [0 ].Title )
2073+ } else {
2074+ // Should succeed
2075+ require .Equal (t , http .StatusCreated , statusCode )
2076+
2077+ var result v1.CreateBlueprintResponse
2078+ err := json .Unmarshal ([]byte (resp ), & result )
2079+ require .NoError (t , err )
2080+ require .NotEmpty (t , result .Id )
2081+ }
2082+ })
2083+ }
2084+ }
0 commit comments