github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/containers/podman/state.go (about)

     1  // Copyright 2025 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  //go:build linux
    16  
    17  package podman
    18  
    19  import (
    20  	"database/sql"
    21  	"encoding/json"
    22  	"errors"
    23  	"fmt"
    24  	"strings"
    25  	"time"
    26  
    27  	bolt "go.etcd.io/bbolt"
    28  
    29  	// SQLite driver needed for parsing db.sql files.
    30  	_ "modernc.org/sqlite"
    31  )
    32  
    33  // state interface must be implemented by each repository returning podman containers info
    34  type state interface {
    35  	Close() error
    36  	AllContainers() ([]*container, error)
    37  }
    38  
    39  var _ state = &boltState{}
    40  
    41  type boltState struct {
    42  	conn *bolt.DB
    43  }
    44  
    45  func newBoltState(path string) (state, error) {
    46  	db, err := bolt.Open(path, 0444, &bolt.Options{Timeout: 1 * time.Second})
    47  	if err != nil {
    48  		return nil, err
    49  	}
    50  	return &boltState{conn: db}, nil
    51  }
    52  
    53  // AllContainers return all the pods
    54  func (s *boltState) AllContainers() ([]*container, error) {
    55  	ctrs := []*container{}
    56  
    57  	err := s.conn.View(func(tx *bolt.Tx) error {
    58  		allCtrsBucket := tx.Bucket([]byte("all-ctrs"))
    59  		if allCtrsBucket == nil {
    60  			return errors.New("allCtrs bucket not found in DB")
    61  		}
    62  
    63  		ctrBuckets := tx.Bucket([]byte("ctr"))
    64  		if ctrBuckets == nil {
    65  			return errors.New("containers bucket not found in DB")
    66  		}
    67  
    68  		return allCtrsBucket.ForEach(func(id, name []byte) error {
    69  			ctrBucket := ctrBuckets.Bucket(id)
    70  			if ctrBucket == nil {
    71  				return fmt.Errorf("state is inconsistent - container ID %s in all containers, but container not found", string(id))
    72  			}
    73  
    74  			ctr := new(container)
    75  			ctr.config = new(containerConfig)
    76  			ctr.state = new(containerState)
    77  
    78  			configBytes := ctrBucket.Get([]byte("config"))
    79  			if err := json.Unmarshal(configBytes, ctr.config); err != nil {
    80  				return fmt.Errorf("unmarshalling container %s config: %w", string(id), err)
    81  			}
    82  
    83  			stateBytes := ctrBucket.Get([]byte("state"))
    84  			if err := json.Unmarshal(stateBytes, ctr.state); err != nil {
    85  				return fmt.Errorf("unmarshalling container %s state: %w", string(id), err)
    86  			}
    87  
    88  			ctrs = append(ctrs, ctr)
    89  			return nil
    90  		})
    91  	})
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  
    96  	return ctrs, nil
    97  }
    98  
    99  // Close closes the bolt db connection
   100  func (s *boltState) Close() error {
   101  	return s.conn.Close()
   102  }
   103  
   104  var _ state = &sqliteState{}
   105  
   106  type sqliteState struct {
   107  	conn *sql.DB
   108  }
   109  
   110  func newSqliteState(path string) (state, error) {
   111  	db, err := sql.Open("sqlite", path)
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  	return &sqliteState{conn: db}, nil
   116  }
   117  
   118  // AllContainers return all the pods
   119  func (s *sqliteState) AllContainers() ([]*container, error) {
   120  	//nolint:noctx // this function is defined by an interface, so we can't pass a context through to here
   121  	rows, err := s.conn.Query("SELECT ContainerConfig.JSON, ContainerState.JSON AS StateJSON FROM ContainerConfig INNER JOIN ContainerState ON ContainerConfig.ID = ContainerState.ID;")
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  	defer rows.Close()
   126  
   127  	var ctrs []*container
   128  	for rows.Next() {
   129  		var configJSON, stateJSON string
   130  		if err := rows.Scan(&configJSON, &stateJSON); err != nil {
   131  			return nil, fmt.Errorf("scanning container from database: %w", err)
   132  		}
   133  
   134  		ctr := new(container)
   135  		ctr.config = new(containerConfig)
   136  		ctr.state = new(containerState)
   137  
   138  		if err := json.Unmarshal([]byte(configJSON), ctr.config); err != nil {
   139  			return nil, fmt.Errorf("unmarshalling container config: %w", err)
   140  		}
   141  		if err := json.Unmarshal([]byte(stateJSON), ctr.state); err != nil {
   142  			return nil, fmt.Errorf("unmarshalling container %s state: %w", ctr.config.ID, err)
   143  		}
   144  		ctrs = append(ctrs, ctr)
   145  	}
   146  	if err := rows.Err(); err != nil {
   147  		return nil, err
   148  	}
   149  	return ctrs, nil
   150  }
   151  
   152  // Close closes the bolt db connection
   153  func (s *sqliteState) Close() error {
   154  	return s.conn.Close()
   155  }
   156  
   157  func getDBState(path string) (state, error) {
   158  	switch {
   159  	case strings.HasSuffix(path, "bolt_state.db"):
   160  		return newBoltState(path)
   161  	case strings.HasSuffix(path, "db.sql"):
   162  		return newSqliteState(path)
   163  	default:
   164  		return nil, fmt.Errorf("cannot create state from %s, database not implemented", path)
   165  	}
   166  }